diff --git a/csireverseproxy/pkg/standaloneproxy/standaloneproxy.go b/csireverseproxy/pkg/standaloneproxy/standaloneproxy.go index e00c0d67..702b1d1c 100644 --- a/csireverseproxy/pkg/standaloneproxy/standaloneproxy.go +++ b/csireverseproxy/pkg/standaloneproxy/standaloneproxy.go @@ -394,6 +394,9 @@ func (revProxy *StandAloneProxy) GetRouter() http.Handler { //Snapshot router.HandleFunc(utils.Prefix+"/{version}/replication/capabilities/symmetrix", revProxy.ifNoSymIDInvoke(revProxy.ServeReplicationCapabilities)) + + // file + router.PathPrefix(utils.InternalPrefix + "/{version}/file/symmetrix/{symid}").HandlerFunc(revProxy.ServeReverseProxy) return revProxy.loggingMiddleware(router) } diff --git a/csireverseproxy/pkg/utils/utils.go b/csireverseproxy/pkg/utils/utils.go index 55c522a7..cf3c204a 100644 --- a/csireverseproxy/pkg/utils/utils.go +++ b/csireverseproxy/pkg/utils/utils.go @@ -41,6 +41,7 @@ const ( StatusNotFound = 404 Prefix = "/univmax/restapi" PrivatePrefix = "/univmax/restapi/private" + InternalPrefix = Prefix + "/internal" ) // WriteHTTPError - given a statuscode and error message, writes a HTTP error using the diff --git a/go.mod b/go.mod index caa74362..512d97fe 100644 --- a/go.mod +++ b/go.mod @@ -16,11 +16,11 @@ require ( github.com/dell/gocsi v1.6.0 github.com/dell/gofsutil v1.9.1-0.20220804050348-2b1cbfd2b12e github.com/dell/goiscsi v1.4.0 - github.com/dell/gopowermax/v2 v2.0.0-20220801063136-61bb0123111e + github.com/dell/gopowermax/v2 v2.0.0-20220818034616-a85ab3cf9394 github.com/fsnotify/fsnotify v1.4.9 github.com/golang/protobuf v1.5.2 github.com/kubernetes-csi/csi-lib-utils v0.7.0 - github.com/sirupsen/logrus v1.8.1 + github.com/sirupsen/logrus v1.9.0 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.7.0 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f @@ -34,7 +34,6 @@ require ( github.com/coreos/go-systemd/v22 v22.3.2 // indirect github.com/cucumber/gherkin-go/v11 v11.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dell/dell-csi-extensions v0.0.0-20220531081201-ea223a47fae4 // indirect github.com/dell/gonvme v1.1.1-0.20220704070618-c22e60197ee5 // indirect github.com/godbus/dbus/v5 v5.0.4 // indirect github.com/gofrs/uuid v4.0.0+incompatible // indirect @@ -71,7 +70,7 @@ require ( golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect google.golang.org/appengine v1.6.1 // indirect diff --git a/go.sum b/go.sum index e25ac9ee..228b3abb 100644 --- a/go.sum +++ b/go.sum @@ -77,34 +77,24 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dell/dell-csi-extensions v0.0.0-20220531081201-ea223a47fae4 h1:hqA86ktBSTWPQenQvHa3fOC9o/xa8q92XOJ6WtJhOew= -github.com/dell/dell-csi-extensions v0.0.0-20220531081201-ea223a47fae4/go.mod h1:aEokaxSRBkNWcbgksCvVLFiiIhJp606m3eHYXbYyeHk= github.com/dell/dell-csi-extensions/common v1.1.0 h1:BFWoXdAennOs+fFiYqsGo02AU27YEYT4jT7YKjWghnw= github.com/dell/dell-csi-extensions/common v1.1.0/go.mod h1:x68COjv2yqphSmGWZDPV9WcBENA9+e8v21aGFO5i3PQ= github.com/dell/dell-csi-extensions/migration v1.0.1 h1:OoKmA/Lj8MaPWfxSdRKL249jaSjKPFfjVr9k9eiwQbI= github.com/dell/dell-csi-extensions/migration v1.0.1/go.mod h1:fJ96sw0lLILdszZJungcxKAHedAjsdbluyLTXOsnGV8= github.com/dell/dell-csi-extensions/replication v1.2.1 h1:dx67k7BBN/aZKgPdOgO04PNFTciChnNwIx+Cnwlgq0s= github.com/dell/dell-csi-extensions/replication v1.2.1/go.mod h1:FU2kzS8/29GlK9iCdS+E70cyeWun63eNP44aHwg7jW8= -github.com/dell/gobrick v1.4.0 h1:YABxiIsHLytMu92tuNqE9apPDBXD8v/RKu5go7gV5XA= -github.com/dell/gobrick v1.4.0/go.mod h1:26AsAebGUd/FuMiQzNM6+8P0IBteipNrAaSYEMVazP8= github.com/dell/gobrick v1.4.1-0.20220704095946-b521c024c35e h1:icsHgB8vjoNsR+h1pWyz6t3nm/O+iPLuSVvJPWjIy/8= github.com/dell/gobrick v1.4.1-0.20220704095946-b521c024c35e/go.mod h1:uzITRMLewN/1VebkFHGR/ouFnbP/e95HqTX7Zi88dvo= github.com/dell/gocsi v1.6.0 h1:ZmoMi17v1jK0RE0OGEivu52/RqHbOhP5cqs9SHExqa0= github.com/dell/gocsi v1.6.0/go.mod h1:+ihwgNYeFTv69Ym2X2Ij1idK72JYoNR8CeiWYJrrbho= -github.com/dell/gofsutil v1.9.0 h1:DyRsHG4wZ9eAG+ywzx+qY8tCvGh3O1/szjPgfi7d/gY= -github.com/dell/gofsutil v1.9.0/go.mod h1:oebB2eaWWF1jniBQpsMGP6qeZcOPjgeWS0WwShS2KWY= github.com/dell/gofsutil v1.9.1-0.20220804050348-2b1cbfd2b12e h1:fw1Es1uTeIQwDdacaf95OsVKg0Bbtb9U6qR1IwQryPM= github.com/dell/gofsutil v1.9.1-0.20220804050348-2b1cbfd2b12e/go.mod h1:oebB2eaWWF1jniBQpsMGP6qeZcOPjgeWS0WwShS2KWY= github.com/dell/goiscsi v1.4.0 h1:TpMo0BLQpHsuUaePHBwZ0yjcq8gXhOOuYeUPzqmwjKk= github.com/dell/goiscsi v1.4.0/go.mod h1:oDkUXDPAYqSOc0t6PpU+BpOqF45mzv+9eLFRkAoQYKs= -github.com/dell/goiscsi v1.4.1-0.20220720190720-020b17e67961 h1:DaTvhFikKPVNUTTMOan69++ZsjQ3PrmRiHRxDXY7iRM= -github.com/dell/goiscsi v1.4.1-0.20220720190720-020b17e67961/go.mod h1:oDkUXDPAYqSOc0t6PpU+BpOqF45mzv+9eLFRkAoQYKs= -github.com/dell/gonvme v1.1.0 h1:5O554dUsrPEXNEy/mc+977eD1FE0geLYlk5HwuXnAaE= -github.com/dell/gonvme v1.1.0/go.mod h1:qLlZNduxrt1hLR/JaU6OXe8+QkU6dtwa5wHM8EWQ58Y= github.com/dell/gonvme v1.1.1-0.20220704070618-c22e60197ee5 h1:9qo+9y4LrLCvKsUoAUkglO/GfvbLWczaePlkwbQPAJs= github.com/dell/gonvme v1.1.1-0.20220704070618-c22e60197ee5/go.mod h1:qLlZNduxrt1hLR/JaU6OXe8+QkU6dtwa5wHM8EWQ58Y= -github.com/dell/gopowermax/v2 v2.0.0-20220801063136-61bb0123111e h1:aQWXHkmAObvafYMljWBNyiCE7pca+6XATOXE4YswdO4= -github.com/dell/gopowermax/v2 v2.0.0-20220801063136-61bb0123111e/go.mod h1:eJEsDN0O0nFqiSdgOXZK73Cv/LlP/+psnkyFSXLR/wI= +github.com/dell/gopowermax/v2 v2.0.0-20220818034616-a85ab3cf9394 h1:OJD9eBFRJxTaEx8+tQrCRn4oJssmXTukzzWNsXcXYBw= +github.com/dell/gopowermax/v2 v2.0.0-20220818034616-a85ab3cf9394/go.mod h1:eJEsDN0O0nFqiSdgOXZK73Cv/LlP/+psnkyFSXLR/wI= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= @@ -368,8 +358,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -526,7 +516,6 @@ golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -540,8 +529,9 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8= +golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/service/controller.go b/service/controller.go index 4f3aaec4..01596ef7 100644 --- a/service/controller.go +++ b/service/controller.go @@ -345,12 +345,30 @@ func (s *service) CreateVolume( replicationEnabled = params[path.Join(s.opts.ReplicationPrefix, RepEnabledParam)] // remote symmetrix ID and rdf group name are mandatory params when replication is enabled remoteSymID = params[path.Join(s.opts.ReplicationPrefix, RemoteSymIDParam)] - localRDFGrpNo = params[path.Join(s.opts.ReplicationPrefix, LocalRDFGroupParam)] - remoteRDFGrpNo = params[path.Join(s.opts.ReplicationPrefix, RemoteRDFGroupParam)] + // check if storage class contains SRDG details + if params[path.Join(s.opts.ReplicationPrefix, LocalRDFGroupParam)] != "" { + localRDFGrpNo = params[path.Join(s.opts.ReplicationPrefix, LocalRDFGroupParam)] + } + if params[path.Join(s.opts.ReplicationPrefix, RemoteRDFGroupParam)] != "" { + remoteRDFGrpNo = params[path.Join(s.opts.ReplicationPrefix, RemoteRDFGroupParam)] + } repMode = params[path.Join(s.opts.ReplicationPrefix, ReplicationModeParam)] remoteServiceLevel = params[path.Join(s.opts.ReplicationPrefix, RemoteServiceLevelParam)] remoteSRPID = params[path.Join(s.opts.ReplicationPrefix, RemoteSRPParam)] bias = params[path.Join(s.opts.ReplicationPrefix, BiasParam)] + + // Get Local and remote RDFg Numbers from a rest call + // Create RDFg for a namespace if it doens't exist? + // Create RDFg when the volume gets added first time for a replication sssn + if localRDFGrpNo == "" && remoteRDFGrpNo == "" { + localRDFGrpNo, remoteRDFGrpNo = s.GetOrCreateRDFGroup(ctx, symmetrixID, remoteSymID, repMode, namespace, pmaxClient) + if localRDFGrpNo == "" || remoteRDFGrpNo == "" { + log.Errorf("Received INVALID RDF Group Numbers LocalRDFg:%s, RemoteRdfg:%s", localRDFGrpNo, remoteRDFGrpNo) + return nil, status.Errorf(codes.NotFound, "Received INVALID RDF Group Numbers LocalRDFg:%s, RemoteRdfg:%s", localRDFGrpNo, remoteRDFGrpNo) + } + log.Debugf("found pre existing group for given array pair and RDF mode: local(%s), remote(%s)", localRDFGrpNo, remoteRDFGrpNo) + + } if repMode == Metro { return s.createMetroVolume(ctx, req, reqID, storagePoolID, symmetrixID, storageGroupName, serviceLevel, thick, remoteSymID, localRDFGrpNo, remoteRDFGrpNo, remoteServiceLevel, remoteSRPID, namespace, applicationPrefix, bias) } @@ -581,7 +599,7 @@ func (s *service) CreateVolume( } var remoteVolumeID string if replicationEnabled == "true" { - remoteVolumeID, err = s.GetRemoteVolumeID(ctx, symmetrixID, localRDFGrpNo, vol.VolumeID, pmaxClient) + remoteVolumeID, _, err = s.GetRemoteVolumeID(ctx, symmetrixID, localRDFGrpNo, vol.VolumeID, pmaxClient) if err != nil && !strings.Contains(err.Error(), "The device must be an RDF device") { return nil, status.Errorf(codes.Internal, "Failed to fetch rdf pair information for (%s) - Error (%s)", vol.VolumeID, err.Error()) } @@ -609,7 +627,7 @@ func (s *service) CreateVolume( "CreationTime": time.Now().Format("20060102150405"), } if replicationEnabled == "true" { - addReplicationParamsToVolumeAttributes(attributes, s.opts.ReplicationContextPrefix, remoteSymID, repMode, remoteVolumeID) + addReplicationParamsToVolumeAttributes(attributes, s.opts.ReplicationContextPrefix, remoteSymID, repMode, remoteVolumeID, localRDFGrpNo, remoteRDFGrpNo) } volResp.VolumeContext = attributes csiResp := &csi.CreateVolumeResponse{ @@ -701,11 +719,11 @@ func (s *service) CreateVolume( "CreationTime": time.Now().Format("20060102150405"), } if replicationEnabled == "true" { - remoteVolumeID, err := s.GetRemoteVolumeID(ctx, symmetrixID, localRDFGrpNo, volID, pmaxClient) + remoteVolumeID, _, err := s.GetRemoteVolumeID(ctx, symmetrixID, localRDFGrpNo, volID, pmaxClient) if err != nil { return nil, status.Errorf(codes.Internal, "Failed to fetch rdf pair information for (%s) - Error (%s)", vol.VolumeID, err.Error()) } - addReplicationParamsToVolumeAttributes(attributes, s.opts.ReplicationContextPrefix, remoteSymID, repMode, remoteVolumeID) + addReplicationParamsToVolumeAttributes(attributes, s.opts.ReplicationContextPrefix, remoteSymID, repMode, remoteVolumeID, localRDFGrpNo, remoteRDFGrpNo) } volResp.VolumeContext = attributes if accessibility != nil { @@ -900,7 +918,7 @@ func (s *service) createMetroVolume(ctx context.Context, req *csi.CreateVolumeRe continue } var remoteVolumeID string - remoteVolumeID, err = s.GetRemoteVolumeID(ctx, symID, localRDFGrpNo, vol.VolumeID, pmaxClient) + remoteVolumeID, _, err = s.GetRemoteVolumeID(ctx, symID, localRDFGrpNo, vol.VolumeID, pmaxClient) if err != nil && !strings.Contains(err.Error(), "The device must be an RDF device") { return nil, status.Errorf(codes.Internal, "Failed to fetch rdf pair information for (%s) - Error (%s)", vol.VolumeID, err.Error()) } @@ -937,7 +955,7 @@ func (s *service) createMetroVolume(ctx context.Context, req *csi.CreateVolumeRe //Format the time output "CreationTime": time.Now().Format("20060102150405"), } - addReplicationParamsToVolumeAttributes(attributes, s.opts.ReplicationContextPrefix, remoteSymID, repMode, remoteVolumeID) + addReplicationParamsToVolumeAttributes(attributes, s.opts.ReplicationContextPrefix, remoteSymID, repMode, remoteVolumeID, localRDFGrpNo, remoteRDFGrpNo) volResp.VolumeContext = attributes csiResp := &csi.CreateVolumeResponse{ Volume: volResp, @@ -987,7 +1005,7 @@ func (s *service) createMetroVolume(ctx context.Context, req *csi.CreateVolumeRe } } - remoteVolumeID, err := s.GetRemoteVolumeID(ctx, symID, localRDFGrpNo, vol.VolumeID, pmaxClient) + remoteVolumeID, _, err := s.GetRemoteVolumeID(ctx, symID, localRDFGrpNo, vol.VolumeID, pmaxClient) if err != nil { log.Errorf("Failed to fetch remote volume details: %s", err.Error()) return nil, err @@ -1033,7 +1051,7 @@ func (s *service) createMetroVolume(ctx context.Context, req *csi.CreateVolumeRe //Format the time output "CreationTime": time.Now().Format("20060102150405"), } - addReplicationParamsToVolumeAttributes(attributes, s.opts.ReplicationContextPrefix, remoteSymID, repMode, remoteVolumeID) + addReplicationParamsToVolumeAttributes(attributes, s.opts.ReplicationContextPrefix, remoteSymID, repMode, remoteVolumeID, localRDFGrpNo, remoteRDFGrpNo) attributes[path.Join(s.opts.ReplicationContextPrefix, RemoteVolumeIDParam)] = remoteVolumeID volResp.VolumeContext = attributes @@ -1047,10 +1065,12 @@ func (s *service) createMetroVolume(ctx context.Context, req *csi.CreateVolumeRe return csiResp, nil } -func addReplicationParamsToVolumeAttributes(attributes map[string]string, prefix, remoteSymID, repMode, remoteVolID string) { +func addReplicationParamsToVolumeAttributes(attributes map[string]string, prefix, remoteSymID, repMode, remoteVolID, localRDFGrpNo, remoteRDFGrpNo string) { attributes[path.Join(prefix, RemoteSymIDParam)] = remoteSymID attributes[path.Join(prefix, ReplicationModeParam)] = repMode attributes[path.Join(prefix, RemoteVolumeIDParam)] = remoteVolID + attributes[path.Join(prefix, LocalRDFGroupParam)] = localRDFGrpNo + attributes[path.Join(prefix, RemoteRDFGroupParam)] = remoteRDFGrpNo } func (s *service) getOrCreateProtectedStorageGroup(ctx context.Context, symID, localProtectionGroupID, namespace, localRDFGrpNo, repMode, reqID string, pmaxClient pmax.Pmax) (*types.RDFStorageGroup, error) { @@ -1066,7 +1086,7 @@ func (s *service) getOrCreateProtectedStorageGroup(ctx context.Context, symID, l sg, err := pmaxClient.GetProtectedStorageGroup(ctx, symID, localProtectionGroupID) if err != nil || sg == nil { // Verify the creation of new protected storage group is valid - err = s.verifyProtectionGroupID(ctx, symID, localProtectionGroupID, namespace, localRDFGrpNo, repMode, pmaxClient) + err = s.verifyProtectionGroupID(ctx, symID, localRDFGrpNo, repMode, pmaxClient) if err != nil { log.Errorf("VerifyProtectionGroupID failed:(%s)", err.Error()) return nil, status.Errorf(codes.Internal, "VerifyProtectionGroupID failed:(%s)", err.Error()) @@ -1083,11 +1103,11 @@ func (s *service) getOrCreateProtectedStorageGroup(ctx context.Context, symID, l } // verifyProtectionGroupID verify's the ProtectionGroupID's uniqueness w.r.t the srdf mode -// For metro mode, one srdf group can only have rdf pairing from one namespace +// For metro mode, one srdf group can have rdf pairing from many namespace // For sync mode, one srdf group can have rdf pairing from many namespaces // For async mode, one srdf group can only have rdf pairing from one namespace // In async rdf mode there should be One to One correspondence between namespace and srdf group -func (s *service) verifyProtectionGroupID(ctx context.Context, symID, storageGroupName, namespace, localRdfGrpNo, repMode string, pmaxClient pmax.Pmax) error { +func (s *service) verifyProtectionGroupID(ctx context.Context, symID, localRdfGrpNo, repMode string, pmaxClient pmax.Pmax) error { sgList, err := pmaxClient.GetStorageGroupIDList(ctx, symID) if err != nil { return err @@ -3112,11 +3132,18 @@ func (s *service) CreateStorageProtectionGroup(ctx context.Context, req *csiext. repMode := params[path.Join(s.opts.ReplicationPrefix, ReplicationModeParam)] remoteRDFGroup := params[path.Join(s.opts.ReplicationPrefix, RemoteRDFGroupParam)] - remoteVolumeID, err := s.GetRemoteVolumeID(ctx, symID, localRDFGroup, devID, pmaxClient) + if len(localRDFGroup) < 1 { + localRDFGroup = strconv.Itoa(vol.RDFGroupIDList[0].RDFGroupNumber) + } + remoteVolumeID, rmRDFG, err := s.GetRemoteVolumeID(ctx, symID, localRDFGroup, devID, pmaxClient) if err != nil { log.Errorf("GetRemoteVolumeID failed with (%s) for devID (%s)", err.Error(), devID) return nil, err } + if len(remoteRDFGroup) < 1 { + remoteRDFGroup = rmRDFG + } + // log all parameters used in CreateStorageProtectionGroup call fields := map[string]interface{}{ "RequestID": reqID, @@ -3248,12 +3275,18 @@ func (s *service) CreateRemoteVolume(ctx context.Context, req *csiext.CreateRemo log.Error("An invalid Remote Service Level parameter was specified") return nil, status.Errorf(codes.InvalidArgument, "An invalid Remote Service Level parameter was specified") } - - remoteVolumeID, err := s.GetRemoteVolumeID(ctx, symID, localRDFGroup, devID, pmaxClient) + // check if localRDFGroup is present in req, else fetch it from volume context + if len(localRDFGroup) < 1 { + localRDFGroup = strconv.Itoa(vol.RDFGroupIDList[0].RDFGroupNumber) + } + remoteVolumeID, rmRDFG, err := s.GetRemoteVolumeID(ctx, symID, localRDFGroup, devID, pmaxClient) if err != nil { log.Errorf("GetRemoteVolumeID failed with (%s) for devID (%s)", err.Error(), devID) return nil, err } + if len(remoteRDFGroup) < 1 { + remoteRDFGroup = rmRDFG + } // Check existence of Storage Group and create if necessary on R2. var remoteStorageGroupName string diff --git a/service/deletion_worker.go b/service/deletion_worker.go index c326aeeb..08df020e 100644 --- a/service/deletion_worker.go +++ b/service/deletion_worker.go @@ -481,7 +481,7 @@ func (queue *deletionQueue) removeVolumesFromStorageGroup(pmaxClient pmax.Pmax) return true } log.Debugf("LocalSG: (%s), Mode: (%s), RDF No: (%s), Namespace: (%s)", sgID, mode, rdfNo, ns) - rdfInfo, err := pmaxClient.GetRDFGroup(context.Background(), queue.SymID, rdfNo) + rdfInfo, err := pmaxClient.GetRDFGroupByID(context.Background(), queue.SymID, rdfNo) if err != nil { log.Errorf("GetRDFGroup failed for (%s) on symID (%s)", sgID, queue.SymID) return false diff --git a/service/replication.go b/service/replication.go index ca454ee2..8b99167c 100644 --- a/service/replication.go +++ b/service/replication.go @@ -17,6 +17,7 @@ package service import ( "context" "fmt" + "strconv" "strings" pmax "github.com/dell/gopowermax/v2" @@ -28,15 +29,170 @@ import ( // Supported actions const ( - Establish = "Establish" - Resume = "Resume" - Suspend = "Suspend" - FailOver = "Failover" - Swap = "Swap" - FailBack = "Failback" - Reprotect = "Reprotect" + Establish = "Establish" + Resume = "Resume" + Suspend = "Suspend" + FailOver = "Failover" + Swap = "Swap" + FailBack = "Failback" + Reprotect = "Reprotect" + QueryRDFMode = "rdf_mode" + QueryRemSymID = "remote_symmetrix_id" + QueryAsync = "Asynchronous" + QuerySync = "Synchronous" + QueryMetro = "Active" ) +func getQueryMode(mode string) string { + switch mode { + case Async: + return QueryAsync + case Sync: + return QuerySync + case Metro: + return QueryMetro + default: + return "" + } +} + +// BuildRdfLabel builds an RDF Label using Local and Remote RDFg +// Format: "csi-" +func buildRdfLabel(mode, namespace string) string { + label := fmt.Sprintf("%s%s", csiPrefix, mode) + if mode == Async { + label = fmt.Sprintf("%s-%s", label, namespace) + } + return label + +} + +// LocalRDFPortsNotAdded checks if the RDF ports are already added to the create RDF payload +// and return accordingly. +func LocalRDFPortsNotAdded(createRDFPayload *types.RDFGroupCreate, localSymID string, dir string, port int) bool { + result := false + localPorts := createRDFPayload.LocalPorts + if len(localPorts) < 1 { + return true + } + for _, prtDetails := range localPorts { + if prtDetails.SymmID != localSymID && prtDetails.DirID != dir && prtDetails.PortNum != port { + result = true + } else { + result = false + } + } + return result +} + +// GetOrCreateRDFGroup get or creates an RDF group automatically. +// 0. Return if there is already a RDFG pair exist +// 1. Get the Next Free RDF group number for Local and Remote +// 2. Get all RDF Directors which are 'ONLINE' from Local Sym +// 3. Get all 'ONLINE' RDF Ports for each obtained 'ONLINE' Director +// 4. SAN SCAN from each 'ONLINE' RDFDir:Port to see which ports on the Remote site are available +// 5. Choose all Ports associated with the remoteSite Provided +// 6. Use all the details above to create a RDFg +func (s *service) GetOrCreateRDFGroup(ctx context.Context, localSymID string, remoteSymID string, repMode string, namespace string, pmaxClient pmax.Pmax) (string, string) { + createRDFgPayload := new(types.RDFGroupCreate) + proceedWithCreate := false + rdfLabel := "" + status := false + + // check if there is already a RDFG exist for symmetrix pair and the mode + rdfLabel = buildRdfLabel(repMode, namespace) + rDFGList, err := pmaxClient.GetRDFGroupList(ctx, localSymID, types.QueryParams{ + QueryRemSymID: remoteSymID, + }) + for _, rDFGID := range rDFGList.RDFGroupIDs { + if strings.Compare(rdfLabel, rDFGID.Label) == 0 { + log.Debugf("found pre existing label for given array pair and RDF mode: %+v", rDFGID) + rDFG, err := pmaxClient.GetRDFGroupByID(ctx, localSymID, strconv.Itoa(rDFGID.RDFGNumber)) + if err != nil { + log.Error(fmt.Sprintf("Failed to fetch RDF pre existing group, Error (%s)", err.Error())) + return "", "" + } + return strconv.Itoa(rDFG.RdfgNumber), strconv.Itoa(rDFG.RemoteRdfgNumber) + } + } + + // Create new RDFG pair, no pre-existing pair for symIDs and rep mode + nextFreeRDFG, err := pmaxClient.GetFreeLocalAndRemoteRDFg(ctx, localSymID, remoteSymID) + if err != nil { + log.Error(fmt.Sprintf("Failed to fetch free RDF groups, Error (%s)", err.Error())) + return "", "" + } + localRDFG := nextFreeRDFG.LocalRdfGroup[0] + remoteRDFG := nextFreeRDFG.RemoteRdfGroup[0] + log.Infof("Fetched Local RDFg:(%d), remote RDFg:(%d)", localRDFG, remoteRDFG) + + // We are only bothered about ONLINE RDF dirs, so get only those + onlineDirList, err := pmaxClient.GetLocalOnlineRDFDirs(ctx, localSymID) + if err != nil { + log.Error(fmt.Sprintf("Failed to fetch local ONLINE RDF Directors, Error (%s)", err.Error())) + return "", "" + } + //For each of the onlineDirs Obtained, get the ONLINE ports + for _, dirs := range onlineDirList.RdfDirs { + onlinePortList, err := pmaxClient.GetLocalOnlineRDFPorts(ctx, dirs, localSymID) + if err != nil { + log.Error(fmt.Sprintf("Unable to get Port list for Online RDF Director:%s", dirs)) + goto EXIT // If the Dir is online we have to get the port list otherwise something gone wrong + } + for _, ports := range onlinePortList.RdfPorts { + // SAN Scan per ONLINE DIR:PORT is quite time-consuming. Check for timeouts. + // Scan time also increases if multiple sites are zoned over the same RDF Port + onlinePortInfo, err := pmaxClient.GetRemoteRDFPortOnSAN(ctx, localSymID, dirs, ports) + if err != nil { + log.Error(fmt.Sprintf("Unable to get Remote Port on SAN for Local RDF port:(%s:%s)", dirs, ports)) + goto EXIT // RDF Dir:Ports were online, yet we didn't get any Remote ports connected on SAN. something is wrong! Exit + } + //Start Building the Req structure if the SAN SCAN reports a hit on the SID of the remoteSymm + for _, remArray := range onlinePortInfo.RemotePorts { + log.Debugf("rem array ports: %+v", remArray) + if remArray.SymmID == remoteSymID { + log.Infof("remote array matched symm we provided:%s", remoteSymID) + // WHen there is a match on the SID, it means from the given local RDFDir:Port + // combo has been zoned to the remote site. So Get the Local Dir:Port and + // build the Local RDF list , if its not already present. + // Build the Array one by one. + ports, _ := strconv.Atoi(ports) + LocalRDFDirPortInfo, err := pmaxClient.GetLocalRDFPortDetails(ctx, localSymID, dirs, ports) + if err != nil { + log.Error(fmt.Sprintf("Unable to get Remote Port on SAN for Local RDF port:(%s:%d)", dirs, ports)) + status = false + goto EXIT + } + log.Infof("checking if dir:%s,port:%d is already added to rdfpayload", dirs, ports) + if LocalRDFPortsNotAdded(createRDFgPayload, localSymID, dirs, ports) { + log.Error(fmt.Sprintf("appending dir:%s,port:%d ", dirs, ports)) + createRDFgPayload.LocalPorts = append(createRDFgPayload.LocalPorts, *LocalRDFDirPortInfo) + } + //Add Remote Ports + createRDFgPayload.RemotePorts = append(createRDFgPayload.RemotePorts, remArray) + } + } + } + proceedWithCreate = true + } + if proceedWithCreate { + // Add the remaining parameters to the create Call and fire it + createRDFgPayload.Label = rdfLabel + createRDFgPayload.LocalRDFNum = localRDFG + createRDFgPayload.RemoteRDFNum = remoteRDFG + + // Fire the call + status = pmaxClient.ExecuteCreateRDFGroup(ctx, localSymID, createRDFgPayload) + goto EXIT + } + +EXIT: + if status == true { + return strconv.Itoa(localRDFG), strconv.Itoa(remoteRDFG) + } + return "", "" +} + // GetRDFDevicePairInfo returns the RDF informtaion of a volume func (s *service) GetRDFDevicePairInfo(ctx context.Context, symID, rdfGrpNo, localVolID string, pmaxClient pmax.Pmax) (*types.RDFDevicePair, error) { rdfPair, err := pmaxClient.GetRDFDevicePairInfo(ctx, symID, rdfGrpNo, localVolID) @@ -65,7 +221,7 @@ func (s *service) ProtectStorageGroup(ctx context.Context, symID, remoteSymID, s } // Proceed to Protect the SG - rdfg, err := pmaxClient.GetRDFGroup(ctx, symID, rdfGrpNo) + rdfg, err := pmaxClient.GetRDFGroupByID(ctx, symID, rdfGrpNo) if err != nil { log.Errorf("Could not get rdf group (%s) information on symID (%s)", symID, rdfGrpNo) return status.Errorf(codes.Internal, "Could not get rdf group (%s) information on symID (%s). Error (%s)", symID, rdfGrpNo, err.Error()) @@ -112,12 +268,12 @@ func (s *service) verifyAndDeleteRemoteStorageGroup(ctx context.Context, remoteS } // GetRemoteVolumeID returns a remote volume ID for give local volume -func (s *service) GetRemoteVolumeID(ctx context.Context, symID, rdfGrpNo, localVolID string, pmaxClient pmax.Pmax) (string, error) { +func (s *service) GetRemoteVolumeID(ctx context.Context, symID, rdfGrpNo, localVolID string, pmaxClient pmax.Pmax) (string, string, error) { rdfPair, err := s.GetRDFDevicePairInfo(ctx, symID, rdfGrpNo, localVolID, pmaxClient) if err != nil { - return "", err + return "", "", err } - return rdfPair.RemoteVolumeName, nil + return rdfPair.RemoteVolumeName, strconv.Itoa(rdfPair.RemoteRdfGroupNumber), nil } // VerifyProtectedGroupDirection returns the direction of protected SG diff --git a/test/integration/features/integration.feature b/test/integration/features/integration.feature index 7a055d1a..84d97b3e 100644 --- a/test/integration/features/integration.feature +++ b/test/integration/features/integration.feature @@ -712,6 +712,10 @@ Scenario: Expand Mount Volume And there are no errors And when I call ExpandVolume to 0 cylinders Then the error message should contain "Invalid argument" + And when I call UnpublishVolume "Node1" + And there are no errors + And when I call DeleteVolume + Then there are no errors And all volumes are deleted successfully @wip @@ -946,3 +950,23 @@ Scenario Outline: Create and Delete 'n' Snapshots from 'n' Volumes in parallel a Then there are no errors And all volumes are deleted successfully +@v2.4.0 + Scenario: Create and delete replication volume with auto SRDF group + Given a Powermax service + And a basic block volume request "integration1" "100" + And adds auto SRDFG replication capability with mode "ASYNC" namespace "INT" + When I call CreateVolume + And I receive a valid volume + And I call CreateStorageProtectionGroup with mode "ASYNC" + And there are no errors + And I call CreateRemoteVolume with mode "ASYNC" + And there are no errors + And when I call DeleteVolume + Then there are no errors + And all volumes are deleted successfully + And I call DeleteTargetVolume + Then there are no errors + And I call Delete LocalStorageProtectionGroup + And there are no errors + And I call Delete RemoteStorageProtectionGroup + And there are no errors diff --git a/test/integration/step_defs_test.go b/test/integration/step_defs_test.go index 55fb4e74..eddbc1c6 100644 --- a/test/integration/step_defs_test.go +++ b/test/integration/step_defs_test.go @@ -207,6 +207,14 @@ func (f *feature) addsReplicationCapability(replicationMode string, namespace st return nil } +func (f *feature) addsAutoSRDFReplicationCapability(replicationMode string, namespace string) error { + f.createVolumeRequest.Parameters[path.Join(f.replicationPrefix, service.RepEnabledParam)] = "true" + f.createVolumeRequest.Parameters[path.Join(f.replicationPrefix, service.RemoteSymIDParam)] = f.remotesymID + f.createVolumeRequest.Parameters[path.Join(f.replicationPrefix, service.ReplicationModeParam)] = replicationMode + f.createVolumeRequest.Parameters[service.CSIPVCNamespace] = namespace + return nil +} + func (f *feature) iCallCreateStorageProtectionGroup(replicationMode string) error { req := new(csiext.CreateStorageProtectionGroupRequest) params := make(map[string]string) @@ -2152,6 +2160,7 @@ func FeatureContext(s *godog.Suite) { s.Step(`^a Powermax service$`, f.aPowermaxService) s.Step(`^a basic block volume request "([^"]*)" "(\d+)"$`, f.aBasicBlockVolumeRequest) s.Step(`^adds replication capability with mode "([^"]*)" namespace "([^"]*)"$`, f.addsReplicationCapability) + s.Step(`^adds auto SRDFG replication capability with mode "([^"]*)" namespace "([^"]*)"$`, f.addsAutoSRDFReplicationCapability) s.Step(`^I call CreateVolume$`, f.iCallCreateVolume) s.Step(`^when I call DeleteVolume$`, f.whenICallDeleteVolume) s.Step(`^there are no errors$`, f.thereAreNoErrors)