From d92821d8c951fe59188ab164553a3ef2db31adad Mon Sep 17 00:00:00 2001 From: jack ma <625198232@qq.com> Date: Thu, 11 Jul 2019 15:08:11 +0800 Subject: [PATCH 1/2] csi test --- .travis.yml | 1 + Gopkg.toml | 4 + csi/server/plugin/opensds/controller.go | 137 +++++--- csi/server/plugin/opensds/fileshare.go | 10 +- csi/server/plugin/opensds/node.go | 6 +- csi/server/plugin/opensds/plugin.go | 7 +- csi/server/plugin/opensds/volume.go | 56 +-- csi/server/sanity/fakeconnector.go | 53 +++ csi/server/sanity/fakeprofilepool.go | 91 +++++ csi/server/sanity/fakereplication.go | 64 ++++ csi/server/sanity/fakevolume.go | 439 ++++++++++++++++++++++++ csi/server/sanity/fakevolume_test.go | 252 ++++++++++++++ csi/server/sanity/sanity_test.go | 122 +++++++ csi/server/sanity/util.go | 81 +++++ csi/server/sanity/util_test.go | 116 +++++++ 15 files changed, 1370 insertions(+), 69 deletions(-) create mode 100644 csi/server/sanity/fakeconnector.go create mode 100644 csi/server/sanity/fakeprofilepool.go create mode 100644 csi/server/sanity/fakereplication.go create mode 100644 csi/server/sanity/fakevolume.go create mode 100644 csi/server/sanity/fakevolume_test.go create mode 100644 csi/server/sanity/sanity_test.go create mode 100644 csi/server/sanity/util.go create mode 100644 csi/server/sanity/util_test.go diff --git a/.travis.yml b/.travis.yml index 29e1ed89c..42b3f6948 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ script: - go test -v github.com/opensds/nbp/csi/... -cover - go test -v github.com/opensds/nbp/flexvolume/... -cover - go test -v github.com/opensds/nbp/cindercompatibleapi/... -cover + - go test -v github.com/opensds/nbp/csi/server/sanity/... -cover after_success: # Clean OpenSDS northbound plugin built data diff --git a/Gopkg.toml b/Gopkg.toml index 561f13a13..079ea3d73 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -77,6 +77,10 @@ name = "k8s.io/client-go" version = "kubernetes-1.13.0-beta.1" +[[constraint]] + name = "github.com/kubernetes-csi/csi-test" + version = "v1.1.0" + [prune] non-go = true go-tests = true diff --git a/csi/server/plugin/opensds/controller.go b/csi/server/plugin/opensds/controller.go index 13608e684..d2dad3c02 100644 --- a/csi/server/plugin/opensds/controller.go +++ b/csi/server/plugin/opensds/controller.go @@ -76,14 +76,14 @@ func (p *Plugin) CreateVolume( func checkInputParameters(params map[string]string) error { if params == nil { - return errors.New("input parameters cannot be nil") + return errors.New("volume creation parameters cannot be nil") } keyList := []string{ParamProfile, ParamEnableReplication, ParamSecondaryAZ, PublishAttachMode, StorageType} for k, _ := range params { if !util.Contained(k, keyList) { - return fmt.Errorf("invalid input paramter key: %s. It should be one of %s,%s,%s,%s,%s", + return fmt.Errorf("invalid volume creation paramter key: %s. It should be one of %s,%s,%s,%s,%s", k, ParamProfile, ParamEnableReplication, ParamSecondaryAZ, PublishAttachMode, StorageType) } } @@ -148,12 +148,22 @@ func (p *Plugin) ControllerPublishVolume(ctx context.Context, return nil, status.Error(codes.InvalidArgument, msg) } - if req.NodeId == "" { + if req.GetNodeId() == "" { msg := "node ID must be provided" glog.Error(msg) return nil, status.Error(codes.InvalidArgument, msg) } + if req.GetVolumeCapability() == nil { + msg := "volume capability must be provided" + glog.Error(msg) + return nil, status.Error(codes.InvalidArgument, msg) + } + + if req.GetReadonly() { + return nil, status.Error(codes.AlreadyExists, "read only volumes are not supported") + } + glog.V(5).Infof("plugin information %#v", p) glog.V(5).Infof("current storage type: %s", p.PluginStorageType) @@ -193,11 +203,29 @@ func (p *Plugin) ControllerUnpublishVolume( } // ValidateVolumeCapabilities implementation -func (p *Plugin) ValidateVolumeCapabilities( - ctx context.Context, +func (p *Plugin) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) ( *csi.ValidateVolumeCapabilitiesResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + + volId := req.GetVolumeId() + if volId == "" { + return nil, status.Error(codes.InvalidArgument, "") + } + + if req.GetVolumeCapabilities() == nil { + return nil, status.Error(codes.InvalidArgument, "") + } + + vol, err := p.Client.GetVolume(volId) + if vol == nil || err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + + return &csi.ValidateVolumeCapabilitiesResponse{ + Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{ + VolumeCapabilities: req.VolumeCapabilities, + }, + }, nil } // ListVolumes implementation @@ -296,6 +324,13 @@ func (p *Plugin) ControllerGetCapabilities( }, }, }, + &csi.ControllerServiceCapability{ + Type: &csi.ControllerServiceCapability_Rpc{ + Rpc: &csi.ControllerServiceCapability_RPC{ + Type: csi.ControllerServiceCapability_RPC_PUBLISH_READONLY, + }, + }, + }, }, }, nil } @@ -426,12 +461,17 @@ func (p *Plugin) DeleteSnapshot( glog.V(5).Infof("start to delete snapshot, snapshot id: %v, delete snapshot secrets: %v!", req.SnapshotId, req.Secrets) - if 0 == len(req.SnapshotId) { + snpId := req.GetSnapshotId() + if snpId == "" { return nil, status.Error(codes.InvalidArgument, "snapshot id cannot be empty") } - err := p.Client.DeleteVolumeSnapshot(req.SnapshotId, nil) + snp, _ := p.Client.GetVolumeSnapshot(snpId) + if snp == nil { + return &csi.DeleteSnapshotResponse{}, nil + } + err := p.Client.DeleteVolumeSnapshot(req.SnapshotId, nil) if nil != err { msg := fmt.Sprintf("delete snapshot failed: %v", err) glog.Error(msg) @@ -488,18 +528,19 @@ func (p *Plugin) ListSnapshots( break case (0 == snapshotIDLen) && (0 != sourceVolumeIdLen): if len(snapshotsFilterByVolumeId) <= 0 { - return nil, status.Error(codes.NotFound, fmt.Sprintf("no snapshot with source volume id %s", sourceVolumeId)) + return &csi.ListSnapshotsResponse{Entries: []*csi.ListSnapshotsResponse_Entry{}}, nil } filterResult = snapshotsFilterByVolumeId break case (0 != snapshotIDLen) && (0 == sourceVolumeIdLen): if len(snapshotsFilterById) <= 0 { - return nil, status.Error(codes.NotFound, fmt.Sprintf("no snapshot with id %s", snapshotId)) + return &csi.ListSnapshotsResponse{Entries: []*csi.ListSnapshotsResponse_Entry{}}, nil } filterResult = snapshotsFilterById break + case (0 != snapshotIDLen) && (0 != sourceVolumeIdLen): case (0 != snapshotIDLen) && (0 != sourceVolumeIdLen): for _, snapshot := range snapshotsFilterById { if snapshot.VolumeId == sourceVolumeId { @@ -687,28 +728,24 @@ func (p *Plugin) UnpublishRoutine() { if err := p.Client.DeleteVolumeAttachment(act.Id, act); err != nil { glog.Errorf("%s failed to unpublish: %v", act.Id, err) - } else { - waitAttachmentDeleted(act.Id, func(id string) (interface{}, error) { - return p.Client.GetVolumeAttachment(id) - }, e) } + waitAttachmentDeleted(act.Id, func(id string) (interface{}, error) { + return p.Client.GetVolumeAttachment(id) + }, e) + // delete fileshare access control list if storage type is file case *model.FileShareAclSpec: act := e.Value.(*model.FileShareAclSpec) if err := p.Client.DeleteFileShareAcl(act.Id); err != nil { - if strings.Contains(err.Error(), "Not Found") { - glog.Infof("delete attachment %s successfully", act.Id) - UnpublishAttachmentList.Delete(e) - } else { - glog.Errorf("%s failed to unpublish: %v", act.Id, err) - } - } else { - waitAttachmentDeleted(act.Id, func(id string) (interface{}, error) { - return p.Client.GetFileShareAcl(id) - }, e) + glog.Errorf("%s failed to unpublish: %v", act.Id, err) } + + waitAttachmentDeleted(act.Id, func(id string) (interface{}, error) { + return p.Client.GetFileShareAcl(id) + }, e) + } time.Sleep(10 * time.Second) @@ -726,16 +763,25 @@ func waitAttachmentDeleted(id string, f func(string) (interface{}, error), e *li for { select { case <-ticker.C: - _, err := f(id) - - if err != nil && strings.Contains(err.Error(), "Not Found") { - glog.Infof("delete attachment %s successfully", id) - UnpublishAttachmentList.Delete(e) - return - } else { - glog.Errorf("delete attachment failed: %v", err) + v, _ := f(id) + + switch v.(type) { + case *model.VolumeAttachmentSpec: + if v.(*model.VolumeAttachmentSpec) == nil { + glog.Infof("delete attachment %s successfully", id) + UnpublishAttachmentList.Delete(e) + return + } + case *model.FileShareAclSpec: + if v.(*model.FileShareAclSpec) == nil { + glog.Infof("delete attachment %s successfully", id) + UnpublishAttachmentList.Delete(e) + return + } } + glog.Errorf("delete attachment %#v failed", v) + case <-timeout: glog.Errorf("waiting to delete %s timeout", id) return @@ -755,14 +801,14 @@ func extractISCSIInitiatorFromNodeInfo(nodeInfo string) (string, error) { } func extractNvmeofInitiatorFromNodeInfo(nodeInfo string) (string, error) { - for _, v := range strings.Split(nodeInfo, ",") { - if strings.Contains(v, "nqn") { - glog.V(5).Info("Nvmeof initiator is ", v) - return v, nil - } - } - - return "", errors.New("no Nvmeof initiators found") + for _, v := range strings.Split(nodeInfo, ",") { + if strings.Contains(v, "nqn") { + glog.V(5).Info("Nvmeof initiator is ", v) + return v, nil + } + } + + return "", errors.New("no Nvmeof initiators found") } func extractFCInitiatorFromNodeInfo(nodeInfo string) ([]string, error) { @@ -782,6 +828,17 @@ func extractFCInitiatorFromNodeInfo(nodeInfo string) ([]string, error) { return wwpns, nil } +func extractIpFromNodeInfo(nodeInfo string) (string, error) { + for _, v := range strings.Split(nodeInfo, ",") { + ip := net.ParseIP(v) + if ip != nil { + return v, nil + } + } + + return "", errors.New("cannot find valid ip address") +} + func getZone(requirement *csi.TopologyRequirement) string { if requirement == nil { return "" diff --git a/csi/server/plugin/opensds/fileshare.go b/csi/server/plugin/opensds/fileshare.go index 1c62fcb3f..d67db1dd3 100644 --- a/csi/server/plugin/opensds/fileshare.go +++ b/csi/server/plugin/opensds/fileshare.go @@ -34,11 +34,15 @@ const ( ) type FileShare struct { - Client *client.Client + Client *client.Client + Mounter connector.Mounter } -func NewFileshare(c *client.Client) *FileShare { - return &FileShare{Client: c} +func NewFileshare(c *client.Client, Mounter connector.Mounter) *FileShare { + return &FileShare{ + Client: c, + Mounter: Mounter, + } } func (f *FileShare) CreateFileShare(req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { diff --git a/csi/server/plugin/opensds/node.go b/csi/server/plugin/opensds/node.go index b5a6e97e0..b44925261 100644 --- a/csi/server/plugin/opensds/node.go +++ b/csi/server/plugin/opensds/node.go @@ -162,7 +162,7 @@ func (p *Plugin) NodeGetInfo( glog.Info("start to get node info") defer glog.Info("end to get node info") - hostName, err := connector.GetHostName() + hostName, err := p.Mounter.GetHostName() if err != nil { msg := fmt.Sprintf("failed to get node name: %v", err) glog.Error(msg) @@ -171,7 +171,7 @@ func (p *Plugin) NodeGetInfo( var initiators []string - volDriverTypes := []string{connector.FcDriver, connector.IscsiDriver, connector.NvmeofDriver} + volDriverTypes := []string{connector.FcDriver, connector.IscsiDriver, connector.NvmeofDriver, connector.SampleDriver} for _, volDriverType := range volDriverTypes { volDriver := connector.NewConnector(volDriverType) @@ -195,7 +195,7 @@ func (p *Plugin) NodeGetInfo( return nil, status.Error(codes.FailedPrecondition, msg) } - nodeId := hostName + "," + strings.Join(initiators, ",") + "," + connector.GetHostIP() + nodeId := hostName + "," + strings.Join(initiators, ",") + "," + p.Mounter.GetHostIP() glog.Infof("node info is %s", nodeId) diff --git a/csi/server/plugin/opensds/plugin.go b/csi/server/plugin/opensds/plugin.go index d80aaa0d9..3683fb048 100644 --- a/csi/server/plugin/opensds/plugin.go +++ b/csi/server/plugin/opensds/plugin.go @@ -19,6 +19,7 @@ import ( "github.com/opensds/nbp/client/opensds" "github.com/opensds/nbp/csi/server/plugin" "github.com/opensds/opensds/client" + "github.com/opensds/opensds/contrib/connector" ) // Plugin define @@ -27,6 +28,7 @@ type Plugin struct { Client *client.Client VolumeClient *Volume FileShareClient *FileShare + Mounter connector.Mounter } func NewServer(endpoint, authStrategy, storageType string) (plugin.Service, error) { @@ -40,8 +42,9 @@ func NewServer(endpoint, authStrategy, storageType string) (plugin.Service, erro p := &Plugin{ PluginStorageType: storageType, Client: client, - VolumeClient: NewVolume(client), - FileShareClient: NewFileshare(client), + VolumeClient: NewVolume(client, connector.GetCommonMounter()), + FileShareClient: NewFileshare(client, connector.GetCommonMounter()), + Mounter: connector.GetCommonMounter(), } // When there are multiple volumes unmount at the same time, diff --git a/csi/server/plugin/opensds/volume.go b/csi/server/plugin/opensds/volume.go index b9a3a15cd..39874972a 100644 --- a/csi/server/plugin/opensds/volume.go +++ b/csi/server/plugin/opensds/volume.go @@ -36,11 +36,15 @@ const ( ) type Volume struct { - Client *client.Client + Client *client.Client + Mounter connector.Mounter } -func NewVolume(c *client.Client) *Volume { - return &Volume{Client: c} +func NewVolume(c *client.Client, Mounter connector.Mounter) *Volume { + return &Volume{ + Client: c, + Mounter: Mounter, + } } func (v *Volume) CreateVolume(req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { @@ -107,6 +111,12 @@ func (v *Volume) CreateVolume(req *csi.CreateVolumeRequest) (*csi.CreateVolumeRe glog.Error(msg) return nil, status.Error(codes.Internal, msg) } + } else { + // Should fail when requesting to create a volume with already existing name and different capacity. + if volExist.Size != volumebody.Size { + return nil, status.Error(codes.AlreadyExists, + fmt.Sprintf("create a volume with already existing name and different capacity")) + } } glog.V(5).Info("waiting until volume is created") @@ -303,25 +313,29 @@ func (v *Volume) ControllerPublishVolume(req *csi.ControllerPublishVolumeRequest case connector.RbdDriver: break case connector.NvmeofDriver: - nqn, err := extractNvmeofInitiatorFromNodeInfo(nodeInfo) - if err != nil { - msg := fmt.Sprintf("extract Nvmeof initiator from node info failed, %v", - err.Error()) - glog.Error(msg) - return nil, status.Error(codes.FailedPrecondition, msg) - } - - initator = nqn - break; + nqn, err := extractNvmeofInitiatorFromNodeInfo(nodeInfo) + if err != nil { + msg := fmt.Sprintf("extract Nvmeof initiator from node info failed, %v", + err.Error()) + glog.Error(msg) + return nil, status.Error(codes.FailedPrecondition, msg) + } + + initator = nqn + break + case connector.SampleDriver: + break default: msg := fmt.Sprintf("protocol:%s not support", protocol) glog.Error(msg) return nil, status.Error(codes.InvalidArgument, msg) } - ipIdx := 2 - // here insert nqn into node info so, ipIdx should be 3 - ipIdx ++ + ip, err := extractIpFromNodeInfo(nodeInfo) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + attachReq := &model.VolumeAttachmentSpec{ VolumeId: req.VolumeId, HostInfo: model.HostInfo{ @@ -329,7 +343,7 @@ func (v *Volume) ControllerPublishVolume(req *csi.ControllerPublishVolumeRequest Platform: runtime.GOARCH, OsType: runtime.GOOS, Initiator: initator, - Ip: strings.Split(nodeInfo, ",")[ipIdx], + Ip: ip, }, Metadata: req.VolumeContext, AccessProtocol: protocol, @@ -751,7 +765,7 @@ func (v *Volume) NodePublishVolume(req *csi.NodePublishVolumeRequest) (*csi.Node } // Mount - mounted, err := connector.IsMounted(mountpoint) + mounted, err := v.Mounter.IsMounted(mountpoint) if err != nil { msg := fmt.Sprintf("failed to check mounted: %v", err) glog.Errorf(msg) @@ -765,7 +779,7 @@ func (v *Volume) NodePublishVolume(req *csi.NodePublishVolumeRequest) (*csi.Node glog.Info("mounting...") - err = connector.Mount(device, mountpoint, fsType, mountFlags) + err = v.Mounter.Mount(device, mountpoint, fsType, mountFlags) if err != nil { msg := fmt.Sprintf("failed to mount: %v", err) glog.Errorf(msg) @@ -799,7 +813,7 @@ func (v *Volume) NodeUnpublishVolume(req *csi.NodeUnpublishVolumeRequest) (*csi. if CSIFilesystem == vol.Metadata[CSIVolumeMode] { // check volume is unmounted - mounted, err := connector.IsMounted(req.TargetPath) + mounted, err := v.Mounter.IsMounted(req.TargetPath) if !mounted { glog.Info("target path is already unmounted") return &csi.NodeUnpublishVolumeResponse{}, nil @@ -807,7 +821,7 @@ func (v *Volume) NodeUnpublishVolume(req *csi.NodeUnpublishVolumeRequest) (*csi. // Umount glog.V(5).Infof("mountpoint:%s", req.TargetPath) - err = connector.Umount(req.TargetPath) + err = v.Mounter.Umount(req.TargetPath) if err != nil { msg := fmt.Sprintf("failed to umount: %v", err) glog.Error(msg) diff --git a/csi/server/sanity/fakeconnector.go b/csi/server/sanity/fakeconnector.go new file mode 100644 index 000000000..11964ac71 --- /dev/null +++ b/csi/server/sanity/fakeconnector.go @@ -0,0 +1,53 @@ +// Copyright 2019 The OpenSDS Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sanity + +import ( + "github.com/opensds/opensds/contrib/connector" +) + +type FakeMounter struct{} + +func getFakeMounter() connector.Mounter { + return &FakeMounter{} +} + +func (*FakeMounter) GetFSType(device string) (string, error) { + return "ext4", nil +} + +func (*FakeMounter) Format(device string, fsType string) error { + return nil +} + +func (*FakeMounter) Mount(device, mountpoint, fsType string, mountFlags []string) error { + return nil +} + +func (*FakeMounter) Umount(mountpoint string) error { + return nil +} + +func (*FakeMounter) GetHostIP() string { + return "127.0.0.1" +} + +func (*FakeMounter) GetHostName() (string, error) { + return "fake-host", nil +} + +func (*FakeMounter) IsMounted(target string) (bool, error) { + return false, nil +} diff --git a/csi/server/sanity/fakeprofilepool.go b/csi/server/sanity/fakeprofilepool.go new file mode 100644 index 000000000..7ff0f3453 --- /dev/null +++ b/csi/server/sanity/fakeprofilepool.go @@ -0,0 +1,91 @@ +// Copyright 2019 The OpenSDS Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sanity + +import ( + "strings" + + "github.com/opensds/opensds/pkg/model" +) + +type fakeProfile struct{} + +func (v *fakeProfile) GetProfile(profID string) (*model.ProfileSpec, error) { + return &model.ProfileSpec{ + BaseModel: &model.BaseModel{ + Id: "1106b972-66ef-11e7-b172-db03f3689c9c", + }, + Name: "default", + Description: "default policy", + StorageType: "block", + }, nil +} + +func (v *fakeProfile) Recv(url, method string, input, output interface{}) error { + switch strings.ToUpper(method) { + case "GET": + out, _ := v.GetProfile("") + return structCopy(out, output) + } + + return nil +} + +type fakePool struct{} + +var pool = &model.StoragePoolSpec{ + BaseModel: &model.BaseModel{ + Id: "084bf71e-a102-11e7-88a8-e31fe6d52248", + }, + Name: "sample-pool-01", + Description: "This is the first sample storage pool for testing", + StorageType: "block", + TotalCapacity: 100, + FreeCapacity: 90, + DockId: "b7602e18-771e-11e7-8f38-dbd6d291f4e0", + Extras: model.StoragePoolExtraSpec{ + IOConnectivity: model.IOConnectivityLoS{ + AccessProtocol: "sample", + }, + }, +} + +func (p *fakePool) ListPools() ([]*model.StoragePoolSpec, error) { + pools := []*model.StoragePoolSpec{ + pool, + } + return pools, nil +} + +func (p *fakePool) GetPool(poolId string) (*model.StoragePoolSpec, error) { + return pool, nil +} + +func (v *fakePool) Recv(url, method string, input, output interface{}) error { + switch strings.ToUpper(method) { + case "GET": + switch output.(type) { + case *model.StoragePoolSpec: + pool, _ := v.GetPool("") + return structCopy(pool, output) + + case *[]*model.StoragePoolSpec: + pools, _ := v.ListPools() + return structListCopy(pools, output) + } + } + + return nil +} diff --git a/csi/server/sanity/fakereplication.go b/csi/server/sanity/fakereplication.go new file mode 100644 index 000000000..00a6338c4 --- /dev/null +++ b/csi/server/sanity/fakereplication.go @@ -0,0 +1,64 @@ +// Copyright 2019 The OpenSDS Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sanity + +import ( + "strings" + + c "github.com/opensds/opensds/client" + "github.com/opensds/opensds/pkg/model" +) + +type fakeReplication struct{} + +func (*fakeReplication) CreateReplication(req c.ReplicationBuilder) ( + *model.ReplicationSpec, error) { + return nil, nil +} + +func (*fakeReplication) GetReplication(replicaId string) ( + *model.ReplicationSpec, error) { + return nil, nil +} + +func (*fakeReplication) ListReplications() ([]*model.ReplicationSpec, error) { + return nil, nil +} + +func (*fakeReplication) DeleteReplication(replicaId string, + req c.ReplicationBuilder) error { + return nil +} + +func (*fakeReplication) UpdateReplication(replicaId string, + req c.ReplicationBuilder) ( + *model.ReplicationSpec, error) { + return nil, nil +} + +func (r *fakeReplication) Recv(url, method string, input, output interface{}) error { + switch strings.ToUpper(method) { + case "POST": + r.CreateReplication(nil) + case "PUT": + r.UpdateReplication("", nil) + case "GET": + r.GetReplication("") + case "DELETE": + return r.DeleteReplication("", nil) + } + + return nil +} diff --git a/csi/server/sanity/fakevolume.go b/csi/server/sanity/fakevolume.go new file mode 100644 index 000000000..a78d8afbc --- /dev/null +++ b/csi/server/sanity/fakevolume.go @@ -0,0 +1,439 @@ +// Copyright 2019 The OpenSDS Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sanity + +import ( + "errors" + "fmt" + "strings" + "time" + + c "github.com/opensds/opensds/client" + "github.com/opensds/opensds/pkg/model" + constants "github.com/opensds/opensds/pkg/utils/constants" + uuid "github.com/satori/go.uuid" +) + +type fakeVolume struct{} + +// used as a database +var volumeList []*model.VolumeSpec + +func (v *fakeVolume) CreateVolume(req c.VolumeBuilder) (*model.VolumeSpec, error) { + vol := &model.VolumeSpec{ + BaseModel: &model.BaseModel{ + Id: uuid.NewV4().String(), + }, + Name: req.Name, + UserId: req.UserId, + Description: req.Description, + Size: req.Size, + Status: "available", + AvailabilityZone: req.AvailabilityZone, + ProfileId: req.ProfileId, + } + volumeList = append(volumeList, vol) + + return vol, nil +} + +func (v *fakeVolume) GetVolume(volID string) (*model.VolumeSpec, error) { + for _, vol := range volumeList { + if vol.Id == volID { + return getNewVolume(vol), nil + } + } + + return nil, fmt.Errorf("volume %s cannot be found", volID) +} + +func getNewVolume(vol *model.VolumeSpec) *model.VolumeSpec { + // Return a new pointer object in order not to affect the metadata + return &model.VolumeSpec{ + BaseModel: &model.BaseModel{ + Id: vol.Id, + }, + Name: vol.Name, + UserId: vol.UserId, + Description: vol.Description, + Size: vol.Size, + Status: vol.Status, + AvailabilityZone: vol.AvailabilityZone, + ProfileId: vol.ProfileId, + } +} + +func (v *fakeVolume) ListVolumes() ([]*model.VolumeSpec, error) { + var volumeListNew []*model.VolumeSpec + + for _, vol := range volumeList { + volumeListNew = append(volumeListNew, getNewVolume(vol)) + } + + return volumeListNew, nil +} + +func (v *fakeVolume) DeleteVolume(volID string, req c.VolumeBuilder) error { + if _, err := v.GetVolume(volID); err != nil { + return err + } + + for i, vol := range volumeList { + if vol.Id == volID { + volumeList = append(volumeList[:i], volumeList[i+1:]...) + break + } + } + + return nil +} + +func (v *fakeVolume) UpdateVolume(volID string, req c.VolumeBuilder) ( + *model.VolumeSpec, error) { + for _, vol := range volumeList { + if vol.Id == req.Id { + vol.Name = req.Name + vol.UserId = req.UserId + vol.Description = req.Description + vol.Size = req.Size + vol.AvailabilityZone = req.AvailabilityZone + vol.ProfileId = req.ProfileId + vol.Status = req.Status + + return getNewVolume(vol), nil + } + } + + return nil, fmt.Errorf("volume %s cannot be found", volID) +} + +func (v *fakeVolume) ExtendVolume(volID string, req c.VolumeBuilder) ( + *model.VolumeSpec, error) { + newSize := req.Size + for _, vol := range volumeList { + if vol.Id == volID { + vol.Size = newSize + return getNewVolume(vol), nil + } + } + + return nil, fmt.Errorf("volume %s cannot be found", volID) +} + +// used as a database +var attachments []*model.VolumeAttachmentSpec + +func (v *fakeVolume) CreateVolumeAttachment(req c.VolumeAttachmentBuilder) ( + *model.VolumeAttachmentSpec, error) { + atcm := &model.VolumeAttachmentSpec{ + BaseModel: &model.BaseModel{ + Id: uuid.NewV4().String(), + }, + VolumeId: req.VolumeId, + Mountpoint: req.Mountpoint, + Status: "available", + Metadata: req.Metadata, + HostInfo: req.HostInfo, + ConnectionInfo: req.ConnectionInfo, + AccessProtocol: req.AccessProtocol, + AttachMode: req.AttachMode, + } + attachments = append(attachments, atcm) + + return atcm, nil +} + +func getNewVolumeAttachment(req *model.VolumeAttachmentSpec) *model.VolumeAttachmentSpec { + // Return a new pointer object in order not to affect the metadata + return &model.VolumeAttachmentSpec{ + BaseModel: &model.BaseModel{ + Id: req.Id, + }, + VolumeId: req.VolumeId, + Mountpoint: req.Mountpoint, + Status: req.Status, + Metadata: req.Metadata, + HostInfo: req.HostInfo, + ConnectionInfo: req.ConnectionInfo, + AccessProtocol: req.AccessProtocol, + AttachMode: req.AttachMode, + } +} + +func (v *fakeVolume) UpdateVolumeAttachment(atcID string, req c.VolumeAttachmentBuilder) ( + *model.VolumeAttachmentSpec, error) { + for _, atcm := range attachments { + if atcm.Id == req.Id { + atcm.VolumeId = req.VolumeId + atcm.Mountpoint = req.Mountpoint + atcm.Status = req.Status + atcm.Metadata = req.Metadata + atcm.HostInfo = req.HostInfo + atcm.ConnectionInfo = req.ConnectionInfo + atcm.AccessProtocol = req.AccessProtocol + atcm.AttachMode = req.AttachMode + + return getNewVolumeAttachment(req), nil + } + } + + return nil, fmt.Errorf("volume attachment %s cannot be found", atcID) +} + +func (v *fakeVolume) GetVolumeAttachment(atcID string) (*model.VolumeAttachmentSpec, error) { + for _, atcm := range attachments { + if atcm.Id == atcID { + // Return a new pointer object in order not to affect the metadata + return getNewVolumeAttachment(atcm), nil + } + } + + return nil, fmt.Errorf("volume attachment %s cannot be found", atcID) +} + +func (v *fakeVolume) ListVolumeAttachments() ([]*model.VolumeAttachmentSpec, error) { + var attachmentsNew []*model.VolumeAttachmentSpec + + for _, atcm := range attachments { + attachmentsNew = append(attachmentsNew, getNewVolumeAttachment(atcm)) + } + + return attachmentsNew, nil +} + +func (v *fakeVolume) DeleteVolumeAttachment(atcID string, req c.VolumeAttachmentBuilder) error { + if _, err := v.GetVolumeAttachment(atcID); err != nil { + return err + } + + for i, atcm := range attachments { + if atcm.Id == atcID { + attachments = append(attachments[:i], attachments[i+1:]...) + break + } + } + + return nil +} + +var snapshots []*model.VolumeSnapshotSpec + +func (v *fakeVolume) CreateVolumeSnapshot(req c.VolumeSnapshotBuilder) ( + *model.VolumeSnapshotSpec, error) { + snp := &model.VolumeSnapshotSpec{ + BaseModel: &model.BaseModel{ + Id: uuid.NewV4().String(), + CreatedAt: time.Now().Format(constants.TimeFormat), + }, + Name: req.Name, + Description: req.Description, + ProfileId: req.ProfileId, + Size: req.Size, + Status: "available", + VolumeId: req.VolumeId, + Metadata: req.Metadata, + } + snapshots = append(snapshots, snp) + + return snp, nil +} + +func getNewSnapshot(snp *model.VolumeSnapshotSpec) *model.VolumeSnapshotSpec { + // Return a new pointer object in order not to affect the metadata + return &model.VolumeSnapshotSpec{ + BaseModel: &model.BaseModel{ + Id: snp.Id, + CreatedAt: snp.CreatedAt, + }, + Name: snp.Name, + Description: snp.Description, + ProfileId: snp.ProfileId, + Size: snp.Size, + Status: snp.Status, + VolumeId: snp.VolumeId, + Metadata: snp.Metadata, + } +} + +func (v *fakeVolume) GetVolumeSnapshot(snpID string) (*model.VolumeSnapshotSpec, error) { + for _, snp := range snapshots { + if snp.Id == snpID { + return getNewSnapshot(snp), nil + } + } + + return nil, fmt.Errorf("snapshot %s cannot be found", snpID) +} + +func (v *fakeVolume) ListVolumeSnapshots() ([]*model.VolumeSnapshotSpec, error) { + var snapshotListNew []*model.VolumeSnapshotSpec + + for _, snp := range snapshots { + snapshotListNew = append(snapshotListNew, getNewSnapshot(snp)) + } + + return snapshotListNew, nil +} + +func (v *fakeVolume) DeleteVolumeSnapshot(snpID string, snp c.VolumeSnapshotBuilder) error { + if _, err := v.GetVolumeSnapshot(snpID); err != nil { + return err + } + + for i, snp := range snapshots { + if snp.Id == snpID { + snapshots = append(snapshots[:i], snapshots[i+1:]...) + break + } + } + + return nil +} + +func (v *fakeVolume) UpdateVolumeSnapshot(snpID string, req c.VolumeSnapshotBuilder) ( + *model.VolumeSnapshotSpec, error) { + for _, snp := range snapshots { + if snp.Id == req.Id { + snp.Name = req.Name + snp.Description = req.Description + snp.ProfileId = req.ProfileId + snp.Size = req.Size + snp.Status = req.Status + snp.VolumeId = req.VolumeId + snp.Metadata = req.Metadata + + return getNewSnapshot(snp), nil + } + } + + return nil, fmt.Errorf("volume snapshot %s cannot be found", snpID) +} + +func (v *fakeVolume) Recv(url, method string, input, output interface{}) error { + urlList := strings.Split(url, "/") + id := urlList[len(urlList)-1] + + switch strings.ToUpper(method) { + case "POST": + return v.post(input, output) + case "PUT": + return v.put(input, output) + case "GET": + return v.get(input, output, id) + case "DELETE": + return v.delete(input, id) + } + return nil +} + +func (v *fakeVolume) post(input, output interface{}) error { + switch output.(type) { + case *model.VolumeSpec: + out, _ := v.CreateVolume(input.(c.VolumeBuilder)) + return structCopy(out, output) + + case *model.VolumeSnapshotSpec: + out, _ := v.CreateVolumeSnapshot(input.(c.VolumeSnapshotBuilder)) + return structCopy(out, output) + + case *model.VolumeAttachmentSpec: + out, _ := v.CreateVolumeAttachment(input.(c.VolumeAttachmentBuilder)) + return structCopy(out, output) + + default: + return errors.New("output format not supported") + } +} + +func (v *fakeVolume) put(input, output interface{}) error { + switch output.(type) { + case *model.VolumeSpec: + out, err := v.UpdateVolume("", input.(c.VolumeBuilder)) + if err != nil { + return err + } + return structCopy(out, output) + + case *model.VolumeSnapshotSpec: + out, err := v.UpdateVolumeSnapshot("", input.(c.VolumeSnapshotBuilder)) + if err != nil { + return err + } + return structCopy(out, output) + + case *model.VolumeAttachmentSpec: + out, err := v.UpdateVolumeAttachment("", input.(c.VolumeAttachmentBuilder)) + if err != nil { + return err + } + return structCopy(out, output) + + default: + return errors.New("output format not supported") + } +} + +func (v *fakeVolume) get(input, output interface{}, id string) error { + switch output.(type) { + case *[]*model.VolumeSpec: + out, _ := v.ListVolumes() + return structListCopy(out, output) + + case *[]*model.VolumeSnapshotSpec: + out, _ := v.ListVolumeSnapshots() + return structListCopy(out, output) + + case *[]*model.VolumeAttachmentSpec: + out, _ := v.ListVolumeAttachments() + return structListCopy(out, output) + + case *model.VolumeSpec: + out, err := v.GetVolume(id) + if err != nil { + return err + } + return structCopy(out, output) + + case *model.VolumeSnapshotSpec: + out, err := v.GetVolumeSnapshot(id) + if err != nil { + return err + } + return structCopy(out, output) + + case *model.VolumeAttachmentSpec: + out, err := v.GetVolumeAttachment(id) + if err != nil { + return err + } + return structCopy(out, output) + + default: + return errors.New("output format not supported") + } +} + +func (v *fakeVolume) delete(input interface{}, id string) error { + switch input.(type) { + case c.VolumeBuilder: + return v.DeleteVolume(id, nil) + case c.VolumeSnapshotBuilder: + return v.DeleteVolumeSnapshot(id, nil) + case c.VolumeAttachmentBuilder: + return v.DeleteVolumeAttachment(id, nil) + default: + return errors.New("input format not supported") + } +} diff --git a/csi/server/sanity/fakevolume_test.go b/csi/server/sanity/fakevolume_test.go new file mode 100644 index 000000000..d3cd78fed --- /dev/null +++ b/csi/server/sanity/fakevolume_test.go @@ -0,0 +1,252 @@ +// Copyright 2019 The OpenSDS Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sanity + +import ( + "fmt" + "reflect" + "testing" + + "github.com/opensds/opensds/pkg/model" +) + +var assertTestResult = func(t *testing.T, expected, got interface{}) { + if !reflect.DeepEqual(expected, got) { + t.Errorf("expected: %#v, got: %#v\n", expected, got) + } +} + +func reset() { + volumeList = []*model.VolumeSpec{} + attachments = []*model.VolumeAttachmentSpec{} + snapshots = []*model.VolumeSnapshotSpec{} +} + +func TestVolume(t *testing.T) { + fakeVol := &fakeVolume{} + + req := &model.VolumeSpec{ + BaseModel: &model.BaseModel{ + Id: "3769855c-a102-11e7-b772-17b880d2f537", + }, + Name: "volume_test", + UserId: "2", + Status: "available", + Description: "volume for test", + Size: 3, + AvailabilityZone: "defaultZone", + ProfileId: "bd5b12a8-a101-11e7-941e-d77981b584d8", + } + + t.Run("get volume failed", func(t *testing.T) { + reset() + volId := "17b880d2f537" + _, err := fakeVol.GetVolume(volId) + assertTestResult(t, fmt.Sprintf("volume %s cannot be found", volId), err.Error()) + }) + + t.Run("normal case", func(t *testing.T) { + reset() + vol, _ := fakeVol.CreateVolume(req) + assertTestResult(t, vol.Name, req.Name) + + volNew, _ := fakeVol.GetVolume(vol.Id) + assertTestResult(t, vol, volNew) + + volListExpected := []*model.VolumeSpec{vol} + volListGot, _ := fakeVol.ListVolumes() + assertTestResult(t, volListExpected, volListGot) + + fakeVol.DeleteVolume(vol.Id, nil) + volListGot, _ = fakeVol.ListVolumes() + assertTestResult(t, 0, len(volListGot)) + }) + + t.Run("delete volume failed", func(t *testing.T) { + reset() + volId := "1" + err := fakeVol.DeleteVolume(volId, nil) + assertTestResult(t, fmt.Sprintf("volume %s cannot be found", volId), err.Error()) + }) + + t.Run("update volume successfully", func(t *testing.T) { + reset() + volGot, _ := fakeVol.CreateVolume(req) + assertTestResult(t, req.Name, volGot.Name) + + newVolName := "volume_update" + volGot.Name = newVolName + volNew, _ := fakeVol.UpdateVolume(volGot.Id, volGot) + assertTestResult(t, newVolName, volNew.Name) + }) + + t.Run("update volume failed", func(t *testing.T) { + reset() + newVolName := "volume_update" + req.Name = newVolName + _, err := fakeVol.UpdateVolume(req.Id, req) + assertTestResult(t, fmt.Sprintf("volume %s cannot be found", req.Id), err.Error()) + }) + + t.Run("extend volume successfully", func(t *testing.T) { + reset() + volGot, _ := fakeVol.CreateVolume(req) + assertTestResult(t, req.Name, volGot.Name) + + newSize := int64(6) + volGot.Size = newSize + volNew, _ := fakeVol.ExtendVolume(volGot.Id, volGot) + assertTestResult(t, int64(newSize), volNew.Size) + }) + + t.Run("extend volume failed", func(t *testing.T) { + reset() + newSize := int64(6) + req.Size = newSize + _, err := fakeVol.ExtendVolume(req.Id, req) + assertTestResult(t, fmt.Sprintf("volume %s cannot be found", req.Id), err.Error()) + }) +} + +func TestAttachment(t *testing.T) { + fakeVol := &fakeVolume{} + + req := &model.VolumeAttachmentSpec{ + BaseModel: &model.BaseModel{ + Id: "3bfaf2cc-a102-11e7-8ecb-63aea739d755", + }, + VolumeId: "3769855c-a102-11e7-b772-17b880d2f537", + Mountpoint: "/mnt", + Status: "available", + AccessProtocol: "iscsi", + AttachMode: "ro", + } + + t.Run("get attachment failed", func(t *testing.T) { + reset() + atcmId := "17b880d2f537" + _, err := fakeVol.GetVolumeAttachment(atcmId) + assertTestResult(t, fmt.Sprintf("volume attachment %s cannot be found", atcmId), err.Error()) + }) + + t.Run("normal case", func(t *testing.T) { + reset() + atcm, _ := fakeVol.CreateVolumeAttachment(req) + assertTestResult(t, atcm.DriverVolumeType, req.DriverVolumeType) + + atcmNew, _ := fakeVol.GetVolumeAttachment(atcm.Id) + assertTestResult(t, atcm, atcmNew) + + atcmListExpected := []*model.VolumeAttachmentSpec{atcm} + atcmListGot, _ := fakeVol.ListVolumeAttachments() + assertTestResult(t, atcmListExpected, atcmListGot) + + fakeVol.DeleteVolumeAttachment(atcm.Id, nil) + atcmListGot, _ = fakeVol.ListVolumeAttachments() + assertTestResult(t, 0, len(atcmListGot)) + }) + + t.Run("delete attachment failed", func(t *testing.T) { + reset() + atcmId := "1" + err := fakeVol.DeleteVolumeAttachment(atcmId, nil) + assertTestResult(t, fmt.Sprintf("volume attachment %s cannot be found", atcmId), err.Error()) + }) + + t.Run("update attachment successfully", func(t *testing.T) { + reset() + atcmGot, _ := fakeVol.CreateVolumeAttachment(req) + assertTestResult(t, req.DriverVolumeType, atcmGot.DriverVolumeType) + + newMountpoint := "/tmp" + atcmGot.Mountpoint = newMountpoint + atcmNew, _ := fakeVol.UpdateVolumeAttachment(atcmGot.Id, atcmGot) + assertTestResult(t, newMountpoint, atcmNew.Mountpoint) + }) + + t.Run("update attachment failed", func(t *testing.T) { + reset() + newMountpoint := "/tmp" + req.Mountpoint = newMountpoint + _, err := fakeVol.UpdateVolumeAttachment(req.Id, req) + assertTestResult(t, fmt.Sprintf("volume attachment %s cannot be found", req.Id), err.Error()) + }) +} + +func TestSnapshot(t *testing.T) { + fakeVol := &fakeVolume{} + + req := &model.VolumeSnapshotSpec{ + BaseModel: &model.BaseModel{ + Id: "3bfaf2cc-a102-11e7-8ecb-63aea739d755", + }, + Name: "snapshot_test", + Description: "snapshot for test", + ProfileId: "3769855c-a102-11e7-b772-17b880d2f537", + Size: 4, + Status: "available", + VolumeId: "bd5b12a8-a101-11e7-941e-d77981b584d8", + } + + t.Run("get volume snapshot failed", func(t *testing.T) { + reset() + snpId := "17b880d2f537" + _, err := fakeVol.GetVolumeSnapshot(snpId) + assertTestResult(t, fmt.Sprintf("snapshot %s cannot be found", snpId), err.Error()) + }) + + t.Run("normal case", func(t *testing.T) { + reset() + snp, _ := fakeVol.CreateVolumeSnapshot(req) + assertTestResult(t, snp.Name, req.Name) + + snpNew, _ := fakeVol.GetVolumeSnapshot(snp.Id) + assertTestResult(t, snp, snpNew) + + snpListExpected := []*model.VolumeSnapshotSpec{snp} + snpListGot, _ := fakeVol.ListVolumeSnapshots() + assertTestResult(t, snpListExpected, snpListGot) + + fakeVol.DeleteVolumeSnapshot(snp.Id, nil) + snpListGot, _ = fakeVol.ListVolumeSnapshots() + assertTestResult(t, 0, len(snpListGot)) + }) + + t.Run("delete volume snapshot failed", func(t *testing.T) { + reset() + snpId := "1" + err := fakeVol.DeleteVolumeSnapshot(snpId, nil) + assertTestResult(t, fmt.Sprintf("snapshot %s cannot be found", snpId), err.Error()) + }) + + t.Run("update volume snapshot successfully", func(t *testing.T) { + reset() + snpGot, _ := fakeVol.CreateVolumeSnapshot(req) + assertTestResult(t, req.Name, snpGot.Name) + + newDescription := "volume snapshot for update" + snpGot.Description = newDescription + snpNew, _ := fakeVol.UpdateVolumeSnapshot(snpGot.Id, snpGot) + assertTestResult(t, newDescription, snpNew.Description) + }) + + t.Run("update volume snapshot failed", func(t *testing.T) { + reset() + newDescription := "volume snapshot for update" + req.Description = newDescription + _, err := fakeVol.UpdateVolumeSnapshot(req.Id, req) + assertTestResult(t, fmt.Sprintf("volume snapshot %s cannot be found", req.Id), err.Error()) + }) +} diff --git a/csi/server/sanity/sanity_test.go b/csi/server/sanity/sanity_test.go new file mode 100644 index 000000000..becb98077 --- /dev/null +++ b/csi/server/sanity/sanity_test.go @@ -0,0 +1,122 @@ +// Copyright 2019 The OpenSDS Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sanity + +import ( + "io/ioutil" + "os" + "os/signal" + "syscall" + "testing" + + csi "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/kubernetes-csi/csi-test/pkg/sanity" + "github.com/opensds/nbp/csi/server/plugin/opensds" + "github.com/opensds/nbp/csi/util" + "github.com/opensds/opensds/client" + c "github.com/opensds/opensds/client" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + + _ "github.com/opensds/opensds/contrib/connector/samplefortest" +) + +//start sanity test for driver +func TestDriver(t *testing.T) { + // Initialize csi plugin + client := &client.Client{ + VolumeMgr: &c.VolumeMgr{Receiver: &fakeVolume{}}, + ReplicationMgr: &c.ReplicationMgr{Receiver: &fakeReplication{}}, + ProfileMgr: &c.ProfileMgr{Receiver: &fakeProfile{}}, + PoolMgr: &c.PoolMgr{Receiver: &fakePool{}}, + } + + fakePlugin := &opensds.Plugin{ + Client: client, + VolumeClient: &opensds.Volume{ + Client: client, + Mounter: getFakeMounter(), + }, + PluginStorageType: "block", + Mounter: getFakeMounter(), + } + + go fakePlugin.UnpublishRoutine() + + // New Grpc Server + s := grpc.NewServer() + + // Register CSI Service + csi.RegisterIdentityServer(s, fakePlugin) + csi.RegisterControllerServer(s, fakePlugin) + csi.RegisterNodeServer(s, fakePlugin) + + // Register reflection Service + reflection.Register(s) + + // Get CSI Endpoint Listener + lis, err := util.GetCSIEndPointListener(util.CSIDefaultEndpoint) + if err != nil { + t.Fatalf("failed to listen: %v", err) + } + + // Remove sock file + sigs := make(chan os.Signal, 1) + signal.Notify(sigs) + go func() { + for sig := range sigs { + if sig == syscall.SIGKILL || + sig == syscall.SIGQUIT || + sig == syscall.SIGHUP || + sig == syscall.SIGTERM || + sig == syscall.SIGINT { + t.Log("exit to serve") + if lis.Addr().Network() == "unix" { + sockfile := lis.Addr().String() + os.RemoveAll(sockfile) + t.Logf("remove sock file: %s", sockfile) + } + } + } + }() + + // Serve Plugin Server + go func() { + t.Logf("start to serve: %s", lis.Addr()) + s.Serve(lis) + }() + + // Initialize the sanity + mountDir, err := ioutil.TempDir("", "temp") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(mountDir) + + mountStageDir, err := ioutil.TempDir("", "temp-stage") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(mountStageDir) + + config := &sanity.Config{ + TargetPath: mountDir, + StagingPath: mountStageDir, + Address: lis.Addr().String(), + TestVolumeParameters: map[string]string{opensds.ParamProfile: "1106b972-66ef-11e7-b172-db03f3689c9c"}, + } + // start test + sanity.Test(t, config) +} diff --git a/csi/server/sanity/util.go b/csi/server/sanity/util.go new file mode 100644 index 000000000..fc87572b4 --- /dev/null +++ b/csi/server/sanity/util.go @@ -0,0 +1,81 @@ +// Copyright 2019 The OpenSDS Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sanity + +import ( + "errors" + "fmt" + "reflect" +) + +func structCopy(source, target interface{}) error { + src, tgt, err := checkSrcAndTgtParam(source, target) + if err != nil { + return err + } + + if src.Kind() != reflect.Struct && tgt.Kind() != reflect.Struct { + return errors.New("source and target must be all struct") + } + + for i := 0; i < src.NumField(); i++ { + srcFiled := src.Type().Field(i) + srcFiledName := srcFiled.Name + tgtFiled := tgt.FieldByName(srcFiledName) + + if !tgtFiled.CanSet() { + return fmt.Errorf("%s field is unaddressable", srcFiledName) + } + tgtFiled.Set(src.Field(i)) + } + + return nil +} + +func structListCopy(source, target interface{}) error { + src, tgt, err := checkSrcAndTgtParam(source, target) + if err != nil { + return err + } + + if src.Kind() != reflect.Slice && tgt.Kind() != reflect.Slice { + return errors.New("source and target must be all slice") + } + + for i := 0; i < src.Len(); i++ { + tgt.Set(reflect.Append(tgt, src.Index(i))) + } + + return nil +} + +func checkSrcAndTgtParam(source, target interface{}) (reflect.Value, reflect.Value, error) { + src := reflect.ValueOf(source) + tgt := reflect.ValueOf(target) + + if src.Kind() == reflect.Ptr { + src = src.Elem() + } + + if tgt.Kind() == reflect.Ptr { + tgt = tgt.Elem() + } + + if !tgt.CanSet() { + return reflect.ValueOf(nil), reflect.ValueOf(nil), fmt.Errorf("target is unaddressable") + } + + return src, tgt, nil +} diff --git a/csi/server/sanity/util_test.go b/csi/server/sanity/util_test.go new file mode 100644 index 000000000..24b18af5e --- /dev/null +++ b/csi/server/sanity/util_test.go @@ -0,0 +1,116 @@ +// Copyright 2019 The OpenSDS Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sanity + +import ( + "testing" + + "github.com/opensds/opensds/pkg/model" +) + +type Volume struct { + Name string + Size int +} + +func TestStructListCopy(t *testing.T) { + var vTgt []*Volume + var vSrc = []*Volume{ + &Volume{ + Name: "test", + Size: 4, + }, + } + + t.Run("struct list copy successfully", func(t *testing.T) { + structListCopy(vSrc, &vTgt) + assertTestResult(t, vSrc, vTgt) + }) + + t.Run("target is unaddressable", func(t *testing.T) { + err := structListCopy(vSrc, vTgt) + assertTestResult(t, "target is unaddressable", err.Error()) + }) + + t.Run("target is unaddressable", func(t *testing.T) { + + pools := []*model.StoragePoolSpec{ + &model.StoragePoolSpec{ + BaseModel: &model.BaseModel{ + Id: "084bf71e-a102-11e7-88a8-e31fe6d52248", + }, + Name: "sample-pool-01", + Description: "This is the first sample storage pool for testing", + StorageType: "block", + TotalCapacity: 100, + FreeCapacity: 90, + DockId: "b7602e18-771e-11e7-8f38-dbd6d291f4e0", + }, + } + var vTgt []*model.StoragePoolSpec + structListCopy(pools, &vTgt) + assertTestResult(t, pools, vTgt) + }) +} + +func TestStructCopy(t *testing.T) { + var vTgt Volume + var vSrc = &Volume{ + Name: "test", + Size: 4, + } + + t.Run("struct copy successfully", func(t *testing.T) { + structCopy(vSrc, &vTgt) + assertTestResult(t, vSrc, &vTgt) + }) + + t.Run("target is unaddressable", func(t *testing.T) { + err := structListCopy(vSrc, vTgt) + assertTestResult(t, "target is unaddressable", err.Error()) + }) + + t.Run("target is unaddressable", func(t *testing.T) { + type test struct { + Name string + id string + } + + var vSrc = &test{ + Name: "test-case", + id: "123", + } + + var vTgt *test + err := structCopy(vSrc, vTgt) + assertTestResult(t, "target is unaddressable", err.Error()) + }) + + t.Run("id field is unaddressable", func(t *testing.T) { + type test struct { + Name string + id string + } + + var vSrc = &test{ + Name: "test-case", + id: "123", + } + + var vTgt test + err := structCopy(vSrc, &vTgt) + assertTestResult(t, "id field is unaddressable", err.Error()) + }) +} From 352c266bbc22bd31b42cf9dc9bc721c28424bd2c Mon Sep 17 00:00:00 2001 From: jack ma <625198232@qq.com> Date: Thu, 11 Jul 2019 15:42:27 +0800 Subject: [PATCH 2/2] fix opensds version --- Gopkg.toml | 2 +- csi/server/plugin/opensds/controller.go | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Gopkg.toml b/Gopkg.toml index 079ea3d73..4fd6a5862 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -79,7 +79,7 @@ [[constraint]] name = "github.com/kubernetes-csi/csi-test" - version = "v1.1.0" + version = "1.1.0" [prune] non-go = true diff --git a/csi/server/plugin/opensds/controller.go b/csi/server/plugin/opensds/controller.go index d2dad3c02..e54c15d89 100644 --- a/csi/server/plugin/opensds/controller.go +++ b/csi/server/plugin/opensds/controller.go @@ -76,14 +76,14 @@ func (p *Plugin) CreateVolume( func checkInputParameters(params map[string]string) error { if params == nil { - return errors.New("volume creation parameters cannot be nil") + return errors.New("input parameters cannot be nil") } keyList := []string{ParamProfile, ParamEnableReplication, ParamSecondaryAZ, PublishAttachMode, StorageType} for k, _ := range params { if !util.Contained(k, keyList) { - return fmt.Errorf("invalid volume creation paramter key: %s. It should be one of %s,%s,%s,%s,%s", + return fmt.Errorf("invalid input paramter key: %s. It should be one of %s,%s,%s,%s,%s", k, ParamProfile, ParamEnableReplication, ParamSecondaryAZ, PublishAttachMode, StorageType) } } @@ -148,7 +148,7 @@ func (p *Plugin) ControllerPublishVolume(ctx context.Context, return nil, status.Error(codes.InvalidArgument, msg) } - if req.GetNodeId() == "" { + if req.NodeId == "" { msg := "node ID must be provided" glog.Error(msg) return nil, status.Error(codes.InvalidArgument, msg) @@ -540,7 +540,6 @@ func (p *Plugin) ListSnapshots( filterResult = snapshotsFilterById break - case (0 != snapshotIDLen) && (0 != sourceVolumeIdLen): case (0 != snapshotIDLen) && (0 != sourceVolumeIdLen): for _, snapshot := range snapshotsFilterById { if snapshot.VolumeId == sourceVolumeId {