Skip to content

Commit

Permalink
add command to get DR prerequisites
Browse files Browse the repository at this point in the history
Signed-off-by: Umanga Chapagain <[email protected]>
  • Loading branch information
umangachapagain committed Jul 9, 2024
1 parent c910ed5 commit cc59bcf
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The ODF CLI tool provides configuration and troubleshooting commands for OpenShi
- `recovery-profile`: Get the recovery profile value.
- `health`: Check health of the cluster and common configuration issues.
- `dr-health [ceph status args]`: Print the ceph status of a peer cluster in a mirroring-enabled environment thereby validating connectivity between ceph clusters. Ceph status args can be optionally passed, such as to change the log level: --debug-ms 1.
- `dr-prereq <PeerManagedClusterName>` : Print the status of pre-requisites for Disaster Recovery between peer clusters.
- `mon-endpoints`: Print mon endpoints.
- `odf purge-osd <ID>`: Permanently remove an OSD from the cluster.
- `odf maintenance`: [Perform maintenance operations](docs/maintenance.md) on mons or OSDs. The mon or OSD deployment will be scaled down and replaced temporarily by a maintenance deployment.
Expand Down
18 changes: 18 additions & 0 deletions cmd/odf/get/dr_prereq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package get

import (
"github.com/red-hat-storage/odf-cli/pkg/odf/dr"
"github.com/spf13/cobra"
)

var drPrereqCmd = &cobra.Command{
Use: "dr-prereq",
Short: "Print the status of pre-requisites for Disaster Recovery between peer clusters.",
DisableFlagParsing: true,
Args: cobra.ExactArgs(1),
Example: "odf get dr-prereq <PeerManagedClusterName>",
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
dr.GetDRPrerequisite(ctx, args[0])
},
}
1 change: 1 addition & 0 deletions cmd/odf/get/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ func init() {
GetCmd.AddCommand(rookCmd)
GetCmd.AddCommand(clusterHealth)
GetCmd.AddCommand(drHealthCmd)
GetCmd.AddCommand(drPrereqCmd)
GetCmd.AddCommand(monEndpoints)
}
8 changes: 8 additions & 0 deletions cmd/odf/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/rook/kubectl-rook-ceph/pkg/logging"
rookclient "github.com/rook/rook/pkg/client/clientset/versioned"
"github.com/spf13/cobra"
submarinerv1alpha1 "github.com/submariner-io/submariner-operator/api/v1alpha1"
submarinerv1 "github.com/submariner-io/submariner/pkg/apis/submariner.io/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
Expand Down Expand Up @@ -51,6 +53,12 @@ func init() {
if err := ocsv1.AddToScheme(scheme); err != nil {
logging.Fatal(err)
}
if err := submarinerv1.AddToScheme(scheme); err != nil {
logging.Fatal(err)
}
if err := submarinerv1alpha1.AddToScheme(scheme); err != nil {
logging.Fatal(err)
}

// Hide autocompletion command
RootCmd.CompletionOptions.DisableDefaultCmd = true
Expand Down
13 changes: 13 additions & 0 deletions docs/get.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The get command supports the following sub-commands:
* [recovery-profile](#recovery-profile)
* [health](#health)
* [dr-health](#dr-health)
* [dr-prereq](#dr-prereq)
* [mon-endpoints](#mon-endpoints)

## recovery-profile
Expand Down Expand Up @@ -133,6 +134,18 @@ images: 4 total
2 replaying
```
## dr-prereq
This command is used to get the status of all the pre-requisites for enabling Disaster Recovery on a pair of clusters. It takes a peer cluster as argument and uses it to compare current cluster configruation with peer cluster configuration. Based on the results, it will show status of all the pre-requisites.
```bash
$ odf get dr-prereq peer-cluster-1

Info: Submariner is installed.
Info: Globalnet is required.
Info: Globalnet is enabled.
```
## mon-endpoints
Prints the mon endpoints
Expand Down
10 changes: 8 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ require (
github.com/rook/kubectl-rook-ceph v0.9.1
github.com/rook/rook v1.14.8
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/submariner-io/submariner v0.17.1
github.com/submariner-io/submariner-operator v0.17.1
golang.org/x/net v0.26.0
k8s.io/api v0.29.3
k8s.io/apimachinery v0.29.3
k8s.io/client-go v0.29.3
sigs.k8s.io/controller-runtime v0.17.2
Expand All @@ -19,6 +24,7 @@ require (
github.com/containernetworking/cni v1.2.0-rc1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.0 // indirect
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.8.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
Expand Down Expand Up @@ -65,12 +71,13 @@ require (
github.com/noobaa/noobaa-operator/v5 v5.0.0-20240319123706-4ee28d614c7c // indirect
github.com/openshift/api v0.0.0-20240328065759-f8aa75d189e1 // indirect
github.com/openshift/custom-resource-status v1.1.2 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rook/rook/pkg/apis v0.0.0-20240513003450-39f88521f0fd // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/submariner-io/admiral v0.17.1 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
Expand All @@ -81,7 +88,6 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.29.3 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240322212309-b815d8309940 // indirect
k8s.io/utils v0.0.0-20240310230437-4693a0247e57 // indirect
Expand Down
14 changes: 10 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -624,8 +624,8 @@ github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8Ay
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc=
github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY=
github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
Expand All @@ -640,8 +640,8 @@ github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ
github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo=
github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0=
github.com/openshift/api v0.0.0-20210105115604-44119421ec6b/go.mod h1:aqU5Cq+kqKKPbDMqxo9FojgDeSpNJI7iuskjXjtojDg=
github.com/openshift/api v0.0.0-20240328065759-f8aa75d189e1 h1:Xn2M7RWiWk1ycxpXovZQNZg1u5212ZAf9h45j63Pvvo=
github.com/openshift/api v0.0.0-20240328065759-f8aa75d189e1/go.mod h1:CxgbWAlvu2iQB0UmKTtRu1YfepRg1/vJ64n2DlIEVz4=
Expand Down Expand Up @@ -764,6 +764,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/submariner-io/admiral v0.17.1 h1:p7XkFfbKaQArlzwzZrU4tW5etGjzjlslLhG1p9b/vgs=
github.com/submariner-io/admiral v0.17.1/go.mod h1:38k64mD4hRBQ2UR1VeaJsrIJmmxa3vo5TdevdEUM93k=
github.com/submariner-io/submariner v0.17.1 h1:2iOoRESrBwFuWba1ycYOUpLlqSdgu2bYqBoaszgPotA=
github.com/submariner-io/submariner v0.17.1/go.mod h1:CkqNW/MqV8TmzILoi7McA67iL5k+obuR+OdSU3lVJSI=
github.com/submariner-io/submariner-operator v0.17.1 h1:F9zWTQKyd0HtQn041eIG6owB2+KorhdVHVFVEh4j6/s=
github.com/submariner-io/submariner-operator v0.17.1/go.mod h1:SAxjLuiGGk85SAmhRNRYhYyobFxP14sYBNR51iCzwHs=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
Expand Down
98 changes: 98 additions & 0 deletions pkg/odf/dr/dr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package dr

import (
"context"
"fmt"
"reflect"

"github.com/red-hat-storage/odf-cli/cmd/odf/root"
"github.com/rook/kubectl-rook-ceph/pkg/logging"
submarinerv1alpha1 "github.com/submariner-io/submariner-operator/api/v1alpha1"
submarinerv1 "github.com/submariner-io/submariner/pkg/apis/submariner.io/v1"
"github.com/submariner-io/submariner/pkg/cidr"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime/pkg/client"
)

const submarinerOperatorNamespace string = "submariner-operator"

func GetDRPrerequisite(ctx context.Context, peerClusterID string) {
submarinerCR, err := isSubmarinerEnabled(ctx, root.CtrlClient)
if err != nil {
logging.Fatal(err)
}

if reflect.DeepEqual(submarinerCR, submarinerv1alpha1.Submariner{}) {
logging.Info("Submariner is not installed.")
} else {
logging.Info("Submariner is installed.")
}

localClusterID := submarinerCR.Status.ClusterID
globalnetRequired, err := isGlobalnetRequired(ctx, root.CtrlClient, localClusterID, peerClusterID)
if err != nil {
logging.Fatal(err)
}
if globalnetRequired {
logging.Info("Globalnet is required.")
} else {
logging.Info("Globalnet is not required.")
}

globalnetEnabled := isGlobalnetEnabled(submarinerCR)
if globalnetEnabled {
logging.Info("Globalnet is enabled.")
} else {
logging.Info("Globalnet is not enabled.")
}
}

func isSubmarinerEnabled(ctx context.Context, client ctrl.Client) (submarinerv1alpha1.Submariner, error) {
submarinerCR := submarinerv1alpha1.Submariner{}
err := client.Get(ctx, types.NamespacedName{Name: "submariner", Namespace: submarinerOperatorNamespace}, &submarinerCR)
if err != nil {
// These errors mean that Submariner is not installed.
// IsNoMatchError -> Submariner CRD is not available.
// IsNotFound -> Submariner CR is not created.
if meta.IsNoMatchError(err) || errors.IsNotFound(err) {
return submarinerCR, nil
}
return submarinerCR, err
}
return submarinerCR, nil
}

func isGlobalnetRequired(ctx context.Context, client ctrl.Client, clusterID, peerClusterID string) (bool, error) {
if clusterID == peerClusterID {
return false, fmt.Errorf("Current ClusterID and peer ClusterID refer to the same cluster. Provide a different peer ClusterID.")
}

clusterCR := &submarinerv1.Cluster{}
err := client.Get(ctx, types.NamespacedName{Name: clusterID, Namespace: submarinerOperatorNamespace}, clusterCR)
if err != nil {
return false, err
}

peerClusterCR := &submarinerv1.Cluster{}
err = client.Get(ctx, types.NamespacedName{Name: peerClusterID, Namespace: submarinerOperatorNamespace}, peerClusterCR)
if err != nil {
return false, err
}

err = cidr.OverlappingSubnets(clusterCR.Spec.ServiceCIDR, clusterCR.Spec.ClusterCIDR,
append(peerClusterCR.Spec.ClusterCIDR, peerClusterCR.Spec.ServiceCIDR...))
if err != nil {
return true, nil
}

return false, nil
}

func isGlobalnetEnabled(submarinerCR submarinerv1alpha1.Submariner) bool {
if submarinerCR.Status.GlobalnetDaemonSetStatus.Status == nil {
return false
}
return submarinerCR.Status.GlobalnetDaemonSetStatus.Status.NumberAvailable > 0
}
122 changes: 122 additions & 0 deletions pkg/odf/dr/dr_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package dr

import (
"reflect"
"testing"

"github.com/stretchr/testify/assert"
submarinerv1alpha1 "github.com/submariner-io/submariner-operator/api/v1alpha1"
submarinerv1 "github.com/submariner-io/submariner/pkg/apis/submariner.io/v1"
"golang.org/x/net/context"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
ctrl "sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
)

func Test_isSubmarinerEnabled(t *testing.T) {
var scheme = runtime.NewScheme()
err := submarinerv1alpha1.AddToScheme(scheme)
assert.NoError(t, err)

ctx := context.TODO()

client := ctrl.NewClientBuilder().WithScheme(scheme).Build()
submarinerCR, err := isSubmarinerEnabled(ctx, client)
assert.NoError(t, err)
assert.True(t, reflect.DeepEqual(submarinerCR, submarinerv1alpha1.Submariner{}))

err = client.Create(ctx, &submarinerv1alpha1.Submariner{ObjectMeta: metav1.ObjectMeta{Name: "submariner", Namespace: "submariner-operator"}})
assert.NoError(t, err)

submarinerCR, err = isSubmarinerEnabled(ctx, client)
assert.NoError(t, err)
assert.False(t, reflect.DeepEqual(submarinerCR, submarinerv1alpha1.Submariner{}))
}

func Test_isGlobalnetRequired(t *testing.T) {
var scheme = runtime.NewScheme()
err := submarinerv1.AddToScheme(scheme)
assert.NoError(t, err)

ctx := context.TODO()

client := ctrl.NewClientBuilder().WithScheme(scheme).Build()
res, err := isGlobalnetRequired(ctx, client, "cluster1", "cluster1")
assert.Error(t, err)
assert.False(t, res)

clusterCR := &submarinerv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1",
Namespace: submarinerOperatorNamespace,
},
Spec: submarinerv1.ClusterSpec{
ServiceCIDR: []string{"172.30.0.0/16"},
ClusterCIDR: []string{"10.128.0.0/14"},
},
}
peerClusterCR := &submarinerv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster2",
Namespace: submarinerOperatorNamespace,
},
Spec: submarinerv1.ClusterSpec{
ServiceCIDR: []string{"172.40.0.0/16"},
ClusterCIDR: []string{"10.138.0.0/14"},
},
}
assert.NoError(t, client.Create(ctx, clusterCR))
assert.NoError(t, client.Create(ctx, peerClusterCR))

res, err = isGlobalnetRequired(ctx, client, "cluster1", "cluster2")
assert.NoError(t, err)
assert.False(t, res)

peerClusterWithOverlappingIP := &submarinerv1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster3",
Namespace: submarinerOperatorNamespace,
},
Spec: submarinerv1.ClusterSpec{
ServiceCIDR: []string{"172.30.0.0/16"},
ClusterCIDR: []string{"10.128.0.0/14"},
},
}
assert.NoError(t, client.Create(ctx, peerClusterWithOverlappingIP))

res, err = isGlobalnetRequired(ctx, client, "cluster1", "cluster3")
assert.NoError(t, err)
assert.True(t, res)

res, err = isGlobalnetRequired(ctx, client, "cluster2", "cluster3")
assert.NoError(t, err)
assert.False(t, res)

client = ctrl.NewClientBuilder().WithInterceptorFuncs(interceptor.Funcs{
Get: func(ctx context.Context, client ctrlclient.WithWatch, key ctrlclient.ObjectKey, obj ctrlclient.Object, opts ...ctrlclient.GetOption) error {
return errors.NewNotFound(submarinerv1.Resource("clusters"), "cluster1")
},
}).WithObjects(clusterCR, peerClusterCR, peerClusterWithOverlappingIP).WithScheme(scheme).Build()
res, err = isGlobalnetRequired(ctx, client, "cluster1", "cluster3")
assert.Error(t, err)
assert.False(t, res)
}

func Test_isGlobalnetEnabled(t *testing.T) {
submarinerCR := submarinerv1alpha1.Submariner{}
assert.False(t, isGlobalnetEnabled(submarinerCR))

submarinerCR.Status.GlobalnetDaemonSetStatus = submarinerv1alpha1.DaemonSetStatusWrapper{
Status: &appsv1.DaemonSetStatus{
NumberAvailable: 0,
},
}
assert.False(t, isGlobalnetEnabled(submarinerCR))

submarinerCR.Status.GlobalnetDaemonSetStatus.Status.NumberAvailable = 1
assert.True(t, isGlobalnetEnabled(submarinerCR))
}

0 comments on commit cc59bcf

Please sign in to comment.