diff --git a/api/api.go b/api/api.go index 978d0f400..e8ad01563 100644 --- a/api/api.go +++ b/api/api.go @@ -1332,6 +1332,14 @@ func (v *VolumeSpec) IsNFSProxyVolume() bool { return v.GetProxySpec() != nil && v.GetProxySpec().NfsSpec != nil } +// GetFADAPodName returns the FlashArray Pod name specified in the Pure Block spec, or empty if any fields are unspecified +func (v *VolumeSpec) GetFADAPodName() string { + if v.GetProxySpec() != nil && v.GetProxySpec().PureBlockSpec != nil { + return v.GetProxySpec().PureBlockSpec.PodName + } + return "" +} + // GetCloneCreatorOwnership returns the appropriate ownership for the // new snapshot and if an update is required func (v *VolumeSpec) GetCloneCreatorOwnership(ctx context.Context) (*Ownership, bool) { diff --git a/api/server/sdk/volume_ops.go b/api/server/sdk/volume_ops.go index cb2f84593..49d44f820 100644 --- a/api/server/sdk/volume_ops.go +++ b/api/server/sdk/volume_ops.go @@ -36,6 +36,9 @@ import ( "google.golang.org/grpc/status" ) +// FADAPodLabelKey is a label added to volume locators in the case of FADA volume clone/snap restore +const FADAPodLabelKey = "pure-pod-name" // Used to plumb in the pod name for volume cloning + // When create is called for an existing volume, this function is called to make sure // the SDK only returns that the volume is ready when the status is UP func (s *VolumeServer) waitForVolumeReady(ctx context.Context, id string) (*api.Volume, error) { @@ -106,7 +109,6 @@ func (s *VolumeServer) create( spec *api.VolumeSpec, additionalCloneLabels map[string]string, ) (string, error) { - // Check if the volume has already been created or is in process of creation volName := locator.GetName() v, err := util.VolumeFromName(ctx, s.driver(ctx), volName) @@ -171,8 +173,18 @@ func (s *VolumeServer) create( } // Create a snapshot from the parent + // Only include the FADA pod label + var labels map[string]string = nil + if locator.GetVolumeLabels() != nil { + if pod, ok := locator.GetVolumeLabels()[FADAPodLabelKey]; ok { + labels = map[string]string{ + FADAPodLabelKey: pod, + } + } + } id, err = s.driver(ctx).Snapshot(ctx, parent.GetId(), false, &api.VolumeLocator{ - Name: volName, + Name: volName, + VolumeLabels: labels, }, false) if err != nil { if err == kvdb.ErrNotFound { @@ -335,7 +347,8 @@ func (s *VolumeServer) Clone( } locator := &api.VolumeLocator{ - Name: req.GetName(), + Name: req.GetName(), + VolumeLabels: req.GetAdditionalLabels(), } source := &api.Source{ Parent: req.GetParentId(), diff --git a/api/spec/spec_handler.go b/api/spec/spec_handler.go index 3f3432161..8ca8ec2f7 100644 --- a/api/spec/spec_handler.go +++ b/api/spec/spec_handler.go @@ -640,6 +640,14 @@ func (d *specHandler) UpdateSpecFromOpts(opts map[string]string, spec *api.Volum case api.SpecBackendVolName: volName := v pureBackendVolName = &volName + case api.SpecPurePodName: + if spec.ProxySpec == nil { + spec.ProxySpec = &api.ProxySpec{} + } + if spec.ProxySpec.PureBlockSpec == nil { + spec.ProxySpec.PureBlockSpec = &api.PureBlockSpec{} + } + spec.ProxySpec.PureBlockSpec.PodName = v case api.SpecPureFileExportRules: if spec.ProxySpec == nil { spec.ProxySpec = &api.ProxySpec{} diff --git a/csi/controller.go b/csi/controller.go index a9a3aa75f..a01079a92 100644 --- a/csi/controller.go +++ b/csi/controller.go @@ -614,10 +614,14 @@ func (s *OsdCsiServer) CreateVolume( } newVolumeId = createResp.VolumeId } else { + clonedMetadata := getClonedPVCMetadata(locator) + if spec.GetFADAPodName() != "" { + clonedMetadata[sdk.FADAPodLabelKey] = spec.GetFADAPodName() + } cloneResp, err := volumes.Clone(ctx, &api.SdkVolumeCloneRequest{ Name: req.GetName(), ParentId: source.Parent, - AdditionalLabels: getClonedPVCMetadata(locator), + AdditionalLabels: clonedMetadata, }) if err != nil { return nil, err diff --git a/csi/controller_test.go b/csi/controller_test.go index a33fc9ea1..89031f2ba 100644 --- a/csi/controller_test.go +++ b/csi/controller_test.go @@ -33,6 +33,7 @@ import ( "github.com/golang/protobuf/ptypes/timestamp" "github.com/libopenstorage/openstorage/api" "github.com/libopenstorage/openstorage/api/mock" + "github.com/libopenstorage/openstorage/api/server/sdk" "github.com/libopenstorage/openstorage/api/spec" authsecrets "github.com/libopenstorage/openstorage/pkg/auth/secrets" mockLoadBalancer "github.com/libopenstorage/openstorage/pkg/loadbalancer/mock" @@ -1983,6 +1984,122 @@ func TestControllerCreateVolumeFromSnapshot(t *testing.T) { assert.Equal(t, mockParentID, volumeInfo.GetVolumeContext()[api.SpecParent]) } +func TestControllerCreateVolumeFromSnapshotFADAPod(t *testing.T) { + // Create server and client connection + s := newTestServer(t) + defer s.Stop() + c := csi.NewControllerClient(s.Conn()) + s.mockClusterEnumerateNode(t, "node-1") + // Setup request + mockParentID := "parendId" + name := "myvol" + pod := "mypod" + size := int64(1234) + req := &csi.CreateVolumeRequest{ + Name: name, + VolumeCapabilities: []*csi.VolumeCapability{ + {}, + }, + CapacityRange: &csi.CapacityRange{ + RequiredBytes: size, + }, + VolumeContentSource: &csi.VolumeContentSource{ + Type: &csi.VolumeContentSource_Snapshot{ + Snapshot: &csi.VolumeContentSource_SnapshotSource{ + SnapshotId: mockParentID, + }, + }, + }, + Secrets: map[string]string{authsecrets.SecretTokenKey: systemUserToken}, + Parameters: map[string]string{ + api.SpecPurePodName: pod, + }, + } + + // Setup mock functions + id := "myid" + snapID := id + "-snap" + gomock.InOrder( + + // First check on parent + s.MockDriver(). + EXPECT(). + Enumerate(&api.VolumeLocator{ + VolumeIds: []string{mockParentID}, + }, nil). + Return([]*api.Volume{{Id: mockParentID}}, nil). + Times(1), + + // VolFromName (name) + s.MockDriver(). + EXPECT(). + Inspect(gomock.Any(), []string{name}). + Return(nil, fmt.Errorf("not found")). + Times(1), + + s.MockDriver(). + EXPECT(). + Enumerate(gomock.Any(), nil). + Return(nil, fmt.Errorf("not found")). + Times(1), + + //VolFromName parent + s.MockDriver(). + EXPECT(). + Inspect(gomock.Any(), gomock.Any()). + Return( + []*api.Volume{{ + Id: mockParentID, + }}, nil). + Times(1), + + // create + s.MockDriver(). + EXPECT(). + Snapshot(gomock.Any(), gomock.Any(), gomock.Any(), &api.VolumeLocator{Name: name, VolumeLabels: map[string]string{sdk.FADAPodLabelKey: pod}}, gomock.Any()). + Return(snapID, nil). + Times(1), + s.MockDriver(). + EXPECT(). + Enumerate(&api.VolumeLocator{ + VolumeIds: []string{snapID}, + }, nil). + Return([]*api.Volume{ + { + Id: id, + Source: &api.Source{Parent: mockParentID}, + }, + }, nil). + Times(2), + + s.MockDriver(). + EXPECT(). + Set(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil). + Times(1), + + s.MockDriver(). + EXPECT(). + Enumerate(gomock.Any(), nil). + Return([]*api.Volume{ + { + Id: id, + Source: &api.Source{Parent: mockParentID}, + }, + }, nil). + Times(1), + ) + + r, err := c.CreateVolume(context.Background(), req) + assert.Nil(t, err) + assert.NotNil(t, r) + volumeInfo := r.GetVolume() + + assert.Equal(t, id, volumeInfo.GetVolumeId()) + assert.NotEqual(t, "true", volumeInfo.GetVolumeContext()[api.SpecSharedv4]) + assert.Equal(t, mockParentID, volumeInfo.GetVolumeContext()[api.SpecParent]) +} + func TestControllerCreateVolumeSnapshotThroughParameters(t *testing.T) { // Create server and client connection s := newTestServer(t)