From 245424830175b63c2cb24571c6fe07aed1e16cff Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Sat, 13 Jul 2019 23:44:51 +0800 Subject: [PATCH] refactor code struct and remove csi-common --- Gopkg.lock | 13 +- Gopkg.toml | 4 - Makefile | 6 +- cmd/disk/main.go | 55 +- deploy/disk/kubernetes/base/node-ds.yaml | 6 - pkg/cloudprovider/cloud_manager.go | 611 ++++++++++++++++++ pkg/cloudprovider/types.go | 74 +++ .../utils.go} | 52 +- pkg/common/calculator.go | 30 + pkg/common/common.go | 17 + .../server.go => pkg/common/rpcserver.go | 52 +- pkg/common/types.go | 19 + pkg/disk/disk.go | 102 --- pkg/disk/driver/driver.go | 178 +++++ pkg/disk/driver/storageclass.go | 151 +++++ pkg/disk/driver/types.go | 91 +++ pkg/disk/driver/utils.go | 164 +++++ pkg/disk/{ => rpcserver}/controllerserver.go | 327 +++++----- pkg/disk/{ => rpcserver}/identityserver.go | 68 +- pkg/disk/{ => rpcserver}/nodeserver.go | 130 ++-- pkg/disk/rpcserver/server.go | 20 + pkg/server/instance/instance_manager.go | 108 ---- pkg/server/snapshot/snapshot_manager.go | 255 -------- pkg/server/snapshot/snapshot_manager_test.go | 187 ------ pkg/server/storageclass/storage_class.go | 141 ---- pkg/server/storageclass/storage_class_test.go | 355 ---------- pkg/server/types.go | 97 --- pkg/server/util.go | 239 ------- pkg/server/util_test.go | 548 ---------------- pkg/server/volume/volume_manager.go | 435 ------------- pkg/server/volume/volume_manager_test.go | 431 ------------ pkg/server/zone/zone_manager.go | 69 -- pkg/server/zone/zone_manager_test.go | 36 -- .../github.com/kubernetes-csi/drivers/LICENSE | 201 ------ .../csi-common/controllerserver-default.go | 79 --- .../drivers/pkg/csi-common/driver.go | 102 --- .../pkg/csi-common/identityserver-default.go | 65 -- .../pkg/csi-common/nodeserver-default.go | 65 -- .../drivers/pkg/csi-common/utils.go | 106 --- 39 files changed, 1689 insertions(+), 4000 deletions(-) create mode 100644 pkg/cloudprovider/cloud_manager.go create mode 100644 pkg/cloudprovider/types.go rename pkg/{server/instance/instance_manager_test.go => cloudprovider/utils.go} (53%) create mode 100644 pkg/common/calculator.go create mode 100644 pkg/common/common.go rename vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/server.go => pkg/common/rpcserver.go (69%) create mode 100644 pkg/common/types.go delete mode 100644 pkg/disk/disk.go create mode 100644 pkg/disk/driver/driver.go create mode 100644 pkg/disk/driver/storageclass.go create mode 100644 pkg/disk/driver/types.go create mode 100644 pkg/disk/driver/utils.go rename pkg/disk/{ => rpcserver}/controllerserver.go (67%) rename pkg/disk/{ => rpcserver}/identityserver.go (52%) rename pkg/disk/{ => rpcserver}/nodeserver.go (75%) create mode 100644 pkg/disk/rpcserver/server.go delete mode 100644 pkg/server/instance/instance_manager.go delete mode 100644 pkg/server/snapshot/snapshot_manager.go delete mode 100644 pkg/server/snapshot/snapshot_manager_test.go delete mode 100644 pkg/server/storageclass/storage_class.go delete mode 100644 pkg/server/storageclass/storage_class_test.go delete mode 100644 pkg/server/types.go delete mode 100644 pkg/server/util.go delete mode 100644 pkg/server/util_test.go delete mode 100644 pkg/server/volume/volume_manager.go delete mode 100644 pkg/server/volume/volume_manager_test.go delete mode 100644 pkg/server/zone/zone_manager.go delete mode 100644 pkg/server/zone/zone_manager_test.go delete mode 100644 vendor/github.com/kubernetes-csi/drivers/LICENSE delete mode 100644 vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/controllerserver-default.go delete mode 100644 vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/driver.go delete mode 100644 vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/identityserver-default.go delete mode 100644 vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/nodeserver-default.go delete mode 100644 vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/utils.go diff --git a/Gopkg.lock b/Gopkg.lock index d615f1aa..2f78f71e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -42,14 +42,6 @@ revision = "5853414e1d4771302e0df10d1870c444c2135799" version = "v0.2.0" -[[projects]] - digest = "1:0bde3fb932a1aa4e12bc43ef91157fcda27dd0fc5d9f309647544ceaec075f48" - name = "github.com/kubernetes-csi/drivers" - packages = ["pkg/csi-common"] - pruneopts = "UT" - revision = "8a4ae6feb47cf7ae29b3dab9e3dc3d1ff796fbf2" - version = "v1.0.2" - [[projects]] digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2" name = "github.com/sirupsen/logrus" @@ -228,11 +220,14 @@ input-imports = [ "github.com/container-storage-interface/spec/lib/go/csi", "github.com/golang/glog", - "github.com/kubernetes-csi/drivers/pkg/csi-common", + "github.com/golang/protobuf/ptypes", + "github.com/golang/protobuf/ptypes/timestamp", + "github.com/kubernetes-csi/csi-lib-utils/protosanitizer", "github.com/yunify/qingcloud-sdk-go/client", "github.com/yunify/qingcloud-sdk-go/config", "github.com/yunify/qingcloud-sdk-go/service", "golang.org/x/net/context", + "google.golang.org/grpc", "google.golang.org/grpc/codes", "google.golang.org/grpc/status", "k8s.io/kubernetes/pkg/util/mount", diff --git a/Gopkg.toml b/Gopkg.toml index df0d3715..a99e38bc 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -41,10 +41,6 @@ name = "k8s.io/kubernetes" version = "1.14.1" -[[constraint]] - name = "github.com/kubernetes-csi/drivers" - version = "1.0.2" - [[constraint]] name = "github.com/container-storage-interface/spec" version = "1.1.0" diff --git a/Makefile b/Makefile index 7f36a35c..33955f5d 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ DISK_IMAGE_NAME=dockerhub.qingcloud.com/csiplugin/csi-qingcloud DISK_IMAGE_VERSION=canary DISK_PLUGIN_NAME=qingcloud-disk-csi-driver ROOT_PATH=$(pwd) -PACKAGE_LIST=./cmd/disk ./pkg/disk ./pkg/server ./pkg/server/instance ./pkg/server/volume ./pkg/server/zone +PACKAGE_LIST=./cmd/... ./pkg/... disk: if [ ! -d ./vendor ]; then dep ensure; fi @@ -54,7 +54,7 @@ fmt: go fmt ${PACKAGE_LIST} fmt-deep: fmt - gofmt -s -w -l ${PACKAGE_LIST} + gofmt -s -w -l ./pkg/cloudprovider/ ./pkg/common/ ./pkg/disk/driver ./pkg/disk/rpcserver sanity-test: ${ROOT_PATH}/csi-sanity --csi.endpoint /var/lib/kubelet/plugins/disk.csi.qingcloud.com/csi.sock --csi.testvolumesize 107374182400 @@ -62,4 +62,4 @@ sanity-test: clean: go clean -r -x rm -rf ./_output - rm -rf deploy/disk/docker/${DISK_PLUGIN_NAME} \ No newline at end of file + rm -rf deploy/disk/docker/${DISK_PLUGIN_NAME} diff --git a/cmd/disk/main.go b/cmd/disk/main.go index 2cf23ad5..05b468b2 100644 --- a/cmd/disk/main.go +++ b/cmd/disk/main.go @@ -18,9 +18,18 @@ package main import ( "flag" - "github.com/yunify/qingcloud-csi/pkg/disk" - "github.com/yunify/qingcloud-csi/pkg/server" + "github.com/golang/glog" + "github.com/yunify/qingcloud-csi/pkg/cloudprovider" + "github.com/yunify/qingcloud-csi/pkg/disk/driver" + "github.com/yunify/qingcloud-csi/pkg/disk/rpcserver" "os" + "time" +) + +const ( + version = "v1.1.0" + defaultProvisionName = "disk.csi.qingcloud.com" + defaultConfigPath = "/etc/config/config.yaml" ) func init() { @@ -29,11 +38,13 @@ func init() { var ( endpoint = flag.String("endpoint", "unix://tmp/csi.sock", "CSI endpoint") - driverName = flag.String("drivername", "csi-qingcloud", "name of the driver") - nodeID = flag.String("nodeid", "", "node id") - config = flag.String("config", "/etc/config/config.yaml", "server config file path") + driverName = flag.String("drivername", defaultProvisionName, "name of the driver") + nodeId = flag.String("nodeid", "", + "If driver cannot get instance ID from /etc/qingcloud/instance-id, we would use this flag.") + configPath = flag.String("config", defaultConfigPath, "server config file path") maxVolume = flag.Int64("maxvolume", 10, "Maximum number of volumes that controller can publish to the node.") + timeout = flag.Duration("timeout", time.Second*60, "timeout duration for retrying, default 60s") ) func main() { @@ -43,7 +54,35 @@ func main() { } func handle() { - cloud := server.NewServerConfig(*nodeID, *config, *maxVolume) - driver := disk.GetDiskDriver() - driver.Run(*driverName, *nodeID, *endpoint, cloud) + // Get Instance Id + instanceId, err := driver.GetInstanceIdFromFile(driver.DefaultInstanceIdFilePath) + if err != nil { + glog.Warningf("Failed to get instance id from file, use --nodeId flag. error: %s", err) + instanceId = *nodeId + } + // Get qingcloud config object + cfg, err := cloudprovider.ReadConfigFromFile(*configPath) + if err != nil { + glog.Fatal(err) + } + cloud, err := cloudprovider.NewCloudManager(cfg) + if err != nil { + glog.Fatal(err) + } + + // Set DiskDriverInput + diskDriverInput := &driver.InitDiskDriverInput{ + Name: *driverName, + Version: version, + NodeId: instanceId, + MaxVolume: *maxVolume, + VolumeCap: driver.DefaultVolumeAccessModeType, + ControllerCap: driver.DefaultControllerServiceCapability, + NodeCap: driver.DefaultNodeServiceCapability, + PluginCap: driver.DefaultPluginCapability, + } + + driver := driver.GetDiskDriver() + driver.InitDiskDriver(diskDriverInput) + rpcserver.Run(driver, cloud, *endpoint) } diff --git a/deploy/disk/kubernetes/base/node-ds.yaml b/deploy/disk/kubernetes/base/node-ds.yaml index 7716c90a..aad64c78 100644 --- a/deploy/disk/kubernetes/base/node-ds.yaml +++ b/deploy/disk/kubernetes/base/node-ds.yaml @@ -95,12 +95,6 @@ spec: - name: server-config mountPath: /etc/config volumes: - # make sure mount propagate feature gate is enabled - - name: kubelet-dir - hostPath: - # In AppCenter, must set path field as below. - # path: /data/var/lib/kubelet - path: /var/lib/kubelet - name: socket-dir hostPath: path: /var/lib/kubelet/plugins/disk.csi.qingcloud.com/ diff --git a/pkg/cloudprovider/cloud_manager.go b/pkg/cloudprovider/cloud_manager.go new file mode 100644 index 00000000..f10bb892 --- /dev/null +++ b/pkg/cloudprovider/cloud_manager.go @@ -0,0 +1,611 @@ +package cloudprovider + +import ( + "fmt" + "github.com/golang/glog" + qcclient "github.com/yunify/qingcloud-sdk-go/client" + qcconfig "github.com/yunify/qingcloud-sdk-go/config" + qcservice "github.com/yunify/qingcloud-sdk-go/service" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type CloudManager interface { + // Snapshot Method + FindSnapshot(snapId string) (snapInfo *qcservice.Snapshot, err error) + FindSnapshotByName(snapName string) (snapInfo *qcservice.Snapshot, err error) + CreateSnapshot(snapName string, volId string) (snapId string, err error) + DeleteSnapshot(snapId string) (err error) + // Volume Method + FindVolume(volId string) (volInfo *qcservice.Volume, err error) + FindVolumeByName(volName string) (volInfo *qcservice.Volume, err error) + CreateVolume(volName string, requestSize int, repl int, volType int) (volId string, err error) + CreateVolumeFromSnapshot(volName string, snapId string) (volId string, err error) + DeleteVolume(volId string) (err error) + IsAttachedToInstance(volId string, instanceId string) (isAttached bool, err error) + AttachVolume(volId string, instanceId string) (err error) + DetachVolume(volId string, instanceId string) (err error) + ResizeVolume(volId string, requestSize int) (err error) + // Util Method + FindInstance(instanceId string) (instanceInfo *qcservice.Instance, err error) + GetZone() (zoneName string) + GetZoneList() (zoneNameList []string, err error) + waitJob(jobId string) (err error) +} + +type cloudManager struct { + instanceService *qcservice.InstanceService + snapshotService *qcservice.SnapshotService + volumeService *qcservice.VolumeService + jobService *qcservice.JobService + cloudService *qcservice.QingCloudService +} + +func NewCloudManager(config *qcconfig.Config) (CloudManager, error) { + // initial qingcloud iaas service + qs, err := qcservice.Init(config) + if err != nil { + return nil, err + } + // create services + is, _ := qs.Instance(config.Zone) + ss, _ := qs.Snapshot(config.Zone) + vs, _ := qs.Volume(config.Zone) + js, _ := qs.Job(config.Zone) + + // initial cloud manager + cm := cloudManager{ + instanceService: is, + snapshotService: ss, + volumeService: vs, + jobService: js, + cloudService: qs, + } + glog.Infof("Succeed to initial cloud manager") + return &cm, nil +} + +// NewCloudManagerFromFile +// Create cloud manager from file +func NewCloudManagerFromFile(filePath string) (CloudManager, error) { + // create config + config, err := ReadConfigFromFile(filePath) + if err != nil { + return nil, err + } + return NewCloudManager(config) +} + +// Find snapshot by snapshot id +// Return: nil, nil: not found snapshot +// snapshot, nil: found snapshot +// nil, error: internal error +func (cm *cloudManager) FindSnapshot(id string) (snapshot *qcservice.Snapshot, err error) { + // Set DescribeSnapshot input + input := qcservice.DescribeSnapshotsInput{} + input.Snapshots = append(input.Snapshots, &id) + // Call describe snapshot + output, err := cm.snapshotService.DescribeSnapshots(&input) + // 1. Error is not equal to nil. + if err != nil { + return nil, err + } + // 2. Return code is not equal to 0. + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return nil, fmt.Errorf("Call IaaS DescribeSnapshot err: snapshot id %s in %s", + id, cm.snapshotService.Config.Zone) + } + switch *output.TotalCount { + // Not found snapshot + case 0: + return nil, nil + // Found one snapshot + case 1: + if *output.SnapshotSet[0].Status == SnapshotStatusCeased || + *output.SnapshotSet[0].Status == SnapshotStatusDeleted { + return nil, nil + } + return output.SnapshotSet[0], nil + // Found duplicate snapshots + default: + return nil, + fmt.Errorf("Call IaaS DescribeSnapshot err: find duplicate snapshot, snapshot id %s in %s", + id, cm.snapshotService.Config.Zone) + } +} + +// Find snapshot by snapshot name +// In Qingcloud IaaS platform, it is possible that two snapshots have the same name. +// In Kubernetes, the CO will set a unique PV name. +// CSI driver take the PV name as a snapshot name. +// Return: nil, nil: not found snapshots +// snapshots, nil: found snapshot +// nil, error: internal error +func (cm *cloudManager) FindSnapshotByName(name string) (snapshot *qcservice.Snapshot, err error) { + if len(name) == 0 { + return nil, nil + } + // Set input arguments + input := qcservice.DescribeSnapshotsInput{} + input.SearchWord = &name + // Call DescribeSnapshot + output, err := cm.snapshotService.DescribeSnapshots(&input) + // Handle error + if err != nil { + return nil, err + } + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return nil, fmt.Errorf("Call IaaS DescribeSnapshots err: snapshot name %s in %s", + name, cm.snapshotService.Config.Zone) + } + // Not found snapshots + for _, v := range output.SnapshotSet { + if *v.SnapshotName != name { + continue + } + if *v.Status == SnapshotStatusCeased || *v.Status == SnapshotStatusDeleted { + continue + } + return v, nil + } + return nil, nil +} + +// CreateSnapshot +// 1. format snapshot size +// 2. create snapshot +// 3. wait job +func (cm *cloudManager) CreateSnapshot(snapshotName string, resourceId string) (snapshotId string, err error) { + // 0. Set CreateSnapshot args + // set input value + input := &qcservice.CreateSnapshotsInput{} + // snapshot name + input.SnapshotName = &snapshotName + // full snapshot + snapshotType := int(SnapshotFull) + input.IsFull = &snapshotType + // resource disk id + input.Resources = []*string{&resourceId} + + // 1. Create snapshot + glog.Infof("Call IaaS CreateSnapshot request snapshot name: %s, zone: %s, resource id %s, is full snapshot %T", + *input.SnapshotName, cm.GetZone(), *input.Resources[0], *input.IsFull == SnapshotFull) + output, err := cm.snapshotService.CreateSnapshots(input) + if err != nil { + return "", err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return "", fmt.Errorf(*output.Message) + } + snapshotId = *output.Snapshots[0] + glog.Infof("Call IaaS CreateSnapshots snapshot name %s snapshot id %s succeed", snapshotName, snapshotId) + return snapshotId, nil +} + +// DeleteSnapshot +// 1. delete snapshot by id +// 2. wait job +func (sm *cloudManager) DeleteSnapshot(snapshotId string) error { + // set input value + input := &qcservice.DeleteSnapshotsInput{} + input.Snapshots = append(input.Snapshots, &snapshotId) + // delete snapshot + glog.Infof("Call IaaS DeleteSnapshot request id: %s, zone: %s", + snapshotId, *sm.snapshotService.Properties.Zone) + output, err := sm.snapshotService.DeleteSnapshots(input) + if err != nil { + return err + } + // wait job + glog.Infof("Call IaaS WaitJob %s", *output.JobID) + if err := sm.waitJob(*output.JobID); err != nil { + return err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return fmt.Errorf(*output.Message) + } + glog.Infof("Call IaaS DeleteSnapshot %s succeed", snapshotId) + return nil +} + +// Find volume by volume ID +// Return: nil, nil: not found volumes +// volume, nil: found volume +// nil, error: internal error +func (cm *cloudManager) FindVolume(id string) (volInfo *qcservice.Volume, err error) { + // Set DescribeVolumes input + input := qcservice.DescribeVolumesInput{} + input.Volumes = append(input.Volumes, &id) + // Call describe volume + output, err := cm.volumeService.DescribeVolumes(&input) + // Error: + // 1. Error is not equal to nil. + if err != nil { + return nil, err + } + // 2. Return code is not equal to 0. + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return nil, + fmt.Errorf("Call IaaS DescribeVolumes err: volume id %s in %s", id, cm.volumeService.Config.Zone) + } + switch *output.TotalCount { + // Not found volumes + case 0: + return nil, nil + // Found one volume + case 1: + if *output.VolumeSet[0].Status == DiskStatusCeased || *output.VolumeSet[0]. + Status == DiskStatusDeleted { + return nil, nil + } + return output.VolumeSet[0], nil + // Found duplicate volumes + default: + return nil, + fmt.Errorf("Call IaaS DescribeVolumes err: find duplicate volumes, volume id %s in %s", + id, cm.volumeService.Config.Zone) + } +} + +// Find volume by volume name +// In Qingcloud IaaS platform, it is possible that two volumes have the same name. +// In Kubernetes, the CO will set a unique PV name. +// CSI driver take the PV name as a volume name. +// Return: nil, nil: not found volumes +// volumes, nil: found volume +// nil, error: internal error +func (cm *cloudManager) FindVolumeByName(name string) (volume *qcservice.Volume, err error) { + if len(name) == 0 { + return nil, nil + } + // Set input arguments + input := qcservice.DescribeVolumesInput{} + input.SearchWord = &name + // Call DescribeVolumes + output, err := cm.volumeService.DescribeVolumes(&input) + // Handle error + if err != nil { + return nil, err + } + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return nil, fmt.Errorf("Call IaaS DescribeVolumes err: volume name %s in %s", + name, cm.volumeService.Config.Zone) + } + // Not found volumes + for _, v := range output.VolumeSet { + if *v.VolumeName != name { + continue + } + if *v.Status == DiskStatusCeased || *v.Status == DiskStatusDeleted { + continue + } + return v, nil + } + return nil, nil +} + +// CreateVolume +// 1. format volume size +// 2. create volume +// 3. wait job +func (cm *cloudManager) CreateVolume(volumeName string, requestSize int, replica int, volType int) (volumeId string, + err error) { + // 0. Set CreateVolume args + // create volume count + count := 1 + // volume replicas + replStr := DiskReplicaTypeName[replica] + // volume provisioner size + size := requestSize + // set input value + input := &qcservice.CreateVolumesInput{ + Count: &count, + Repl: &replStr, + Size: &size, + VolumeName: &volumeName, + VolumeType: &volType, + } + // 1. Create volume + glog.Infof("Call IaaS CreateVolume request size: %d GB, zone: %s, type: %d, count: %d, replica: %s, name: %s", + *input.Size, *cm.volumeService.Properties.Zone, *input.VolumeType, *input.Count, *input.Repl, *input.VolumeName) + output, err := cm.volumeService.CreateVolumes(input) + if err != nil { + return "", err + } + // wait job + glog.Infof("Call IaaS WaitJob %s", *output.JobID) + if err := cm.waitJob(*output.JobID); err != nil { + return "", err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return "", fmt.Errorf(*output.Message) + } + volumeId = *output.Volumes[0] + glog.Infof("Call IaaS CreateVolume name %s id %s succeed", volumeName, volumeId) + return *output.Volumes[0], nil +} + +// CreateVolumeFromSnapshot +// In QingCloud, the volume size created from snapshot is equal to original volume. +func (cm *cloudManager) CreateVolumeFromSnapshot(volumeName string, snapshotId string) (volumeId string, err error) { + input := &qcservice.CreateVolumeFromSnapshotInput{ + VolumeName: &volumeName, + Snapshot: &snapshotId, + } + glog.Infof("Call IaaS CreateVolumeFromSnapshot request volume name: %s, snapshot id: %s\n", + *input.VolumeName, *input.Snapshot) + output, err := cm.snapshotService.CreateVolumeFromSnapshot(input) + if err != nil { + return "", err + } + // wait job + glog.Infof("Call IaaS WaitJob %s", *output.JobID) + if err := cm.waitJob(*output.JobID); err != nil { + return "", err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return "", fmt.Errorf(*output.Message) + } + glog.Infof("Call IaaS CreateVolumeFromSnapshot succeed, volume id %s", *output.VolumeID) + return *output.VolumeID, nil +} + +// DeleteVolume +// 1. delete volume by id +// 2. wait job +func (cm *cloudManager) DeleteVolume(id string) error { + // set input value + input := &qcservice.DeleteVolumesInput{} + input.Volumes = append(input.Volumes, &id) + // delete volume + glog.Infof("Call IaaS DeleteVolume request id: %s, zone: %s", + id, *cm.volumeService.Properties.Zone) + output, err := cm.volumeService.DeleteVolumes(input) + if err != nil { + return err + } + // wait job + glog.Infof("Call IaaS WaitJob %s", *output.JobID) + if err := cm.waitJob(*output.JobID); err != nil { + return err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return fmt.Errorf(*output.Message) + } + glog.Infof("Call IaaS DeleteVolume %s succeed", id) + return nil +} + +// IsAttachedToInstance +// 1. get volume information +// 2. compare input instance id with instance field in volume information +func (cm *cloudManager) IsAttachedToInstance(volumeId string, instanceId string) (flag bool, err error) { + // zone + zone := cm.volumeService.Config.Zone + + // get volume item + volumeItem, err := cm.FindVolume(volumeId) + if err != nil { + return false, status.Errorf(codes.Internal, err.Error()) + } + // check volume exist + if volumeItem == nil { + return false, status.Errorf( + codes.NotFound, "Volume %s not found in %s", volumeId, zone) + } + + if volumeItem.Instance != nil && *volumeItem.Instance.InstanceID == instanceId { + return true, nil + } + return false, nil +} + +// AttachVolume +// 1. get volume information +// 2. attach volume on instance +// 3. wait job +func (cm *cloudManager) AttachVolume(volumeId string, instanceId string) error { + zone := cm.GetZone() + // check volume status + vol, err := cm.FindVolume(volumeId) + if err != nil { + return err + } + if vol == nil { + return fmt.Errorf("Cannot found volume %s", volumeId) + } + if *vol.Instance.InstanceID == "" { + // set input parameter + input := &qcservice.AttachVolumesInput{} + input.Volumes = append(input.Volumes, &volumeId) + input.Instance = &instanceId + // attach volume + glog.Infof("Call IaaS AttachVolume request volume id: %s, instance id: %s, zone: %s", volumeId, instanceId, zone) + output, err := cm.volumeService.AttachVolumes(input) + if err != nil { + return err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return fmt.Errorf(*output.Message) + } + // wait job + glog.Infof("Call IaaS WaitJob %s", *output.JobID) + return cm.waitJob(*output.JobID) + } else { + if *vol.Instance.InstanceID == instanceId { + return nil + } + return fmt.Errorf("volume %s has been attached to another instance %s", volumeId, *vol.Instance.InstanceID) + } +} + +// detach volume +// 1. get volume information +// 2. If volume not attached, return nil. +// If volume attached, check instance id. +// 3. attach volume +// 4. wait job +func (cm *cloudManager) DetachVolume(volumeId string, instanceId string) error { + zone := *cm.volumeService.Properties.Zone + // check volume status + vol, err := cm.FindVolume(volumeId) + if err != nil { + return err + } + if vol == nil { + return fmt.Errorf("Cannot found volume %s", volumeId) + } + if *vol.Instance.InstanceID == "" { + return nil + } else { + if *vol.Instance.InstanceID == instanceId || instanceId == "" { + // set input parameter + input := &qcservice.DetachVolumesInput{} + input.Volumes = append(input.Volumes, &volumeId) + input.Instance = vol.Instance.InstanceID + // attach volume + glog.Infof("Call IaaS DetachVolume request volume id: %s, instance id: %s, zone: %s", volumeId, instanceId, zone) + output, err := cm.volumeService.DetachVolumes(input) + if err != nil { + return err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return fmt.Errorf(*output.Message) + } + // wait job + glog.Infof("Call IaaS WaitJob %s", *output.JobID) + return cm.waitJob(*output.JobID) + } + return fmt.Errorf("Volume %s has been attached to another instance %s", volumeId, *vol.Instance.InstanceID) + } +} + +// ResizeVolume can expand the size of a volume offline +// requestSize: GB +func (cm *cloudManager) ResizeVolume(volumeId string, requestSize int) error { + zone := *cm.volumeService.Properties.Zone + // check volume status + vol, err := cm.FindVolume(volumeId) + if err != nil { + return err + } + if vol == nil { + return fmt.Errorf("ResizeVolume: Cannot found volume %s", volumeId) + } + + // resize + glog.Infof("Call Iaas ResizeVolume request volume [%s], size [%d Gib] in zone [%s]", + volumeId, requestSize, zone) + input := &qcservice.ResizeVolumesInput{} + input.Size = &requestSize + input.Volumes = []*string{&volumeId} + output, err := cm.volumeService.ResizeVolumes(input) + if err != nil { + return err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return fmt.Errorf("ResizeVolume: " + *output.Message) + } + // wait job + glog.Infof("Call IaaS WaitJob %s", *output.JobID) + if err := cm.waitJob(*output.JobID); err != nil { + return err + } + // check output + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return fmt.Errorf("ResizeVolume: " + *output.Message) + } + glog.Infof("Call IaaS ResizeVolume id %s size %d succeed", volumeId, requestSize) + return nil +} + +// Find instance by instance ID +// Return: nil, nil: not found instance +// instance, nil: found instance +// nil, error: internal error +func (cm *cloudManager) FindInstance(id string) (instance *qcservice.Instance, err error) { + // set describe instance input + input := qcservice.DescribeInstancesInput{} + var seeCluster int = 1 + input.IsClusterNode = &seeCluster + input.Instances = append(input.Instances, &id) + // call describe instance + output, err := cm.instanceService.DescribeInstances(&input) + // error + if err != nil { + return nil, err + } + if *output.RetCode != 0 { + glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return nil, fmt.Errorf(*output.Message) + } + // not found instances + switch *output.TotalCount { + case 0: + return nil, nil + case 1: + if *output.InstanceSet[0].Status == InstanceStatusCreased || *output.InstanceSet[0].Status == InstanceStatusTerminated { + return nil, nil + } + return output.InstanceSet[0], nil + default: + return nil, fmt.Errorf("find duplicate instances id %s in %s", id, cm.instanceService.Config.Zone) + } +} + +// GetZone +// Get current zone in Qingcloud IaaS +func (cm *cloudManager) GetZone() string { + if cm == nil || cm.cloudService.Config.Zone == "" { + return "" + } + return cm.cloudService.Config.Zone +} + +// GetZoneList gets active zone list +func (zm *cloudManager) GetZoneList() (zones []string, err error) { + output, err := zm.cloudService.DescribeZones(&qcservice.DescribeZonesInput{}) + // Error: + // 1. Error is not equal to nil. + if err != nil { + return nil, err + } + if output == nil { + glog.Errorf("should not response [%#v]", output) + } + for i := range output.ZoneSet { + if *output.ZoneSet[i].Status == ZoneStatusActive { + zones = append(zones, *output.ZoneSet[i].ZoneID) + } + } + return zones, nil +} + +func (cm *cloudManager) waitJob(jobId string) error { + err := qcclient.WaitJob(cm.jobService, jobId, WaitJobTimeout, WaitJobInterval) + if err != nil { + return fmt.Errorf("call IaaS WaitJob id %s, error: ", err) + } + return nil +} diff --git a/pkg/cloudprovider/types.go b/pkg/cloudprovider/types.go new file mode 100644 index 00000000..11ae5c4f --- /dev/null +++ b/pkg/cloudprovider/types.go @@ -0,0 +1,74 @@ +package cloudprovider + +import "time" + +const ( + // In Qingcloud bare host, the path of the file containing instance id. + RetryString = "please try later" + WaitJobInterval = 10 * time.Second + WaitJobTimeout = 180 * time.Second +) + +// Instance +const ( + InstanceStatusPending string = "pending" + InstanceStatusRunning string = "running" + InstanceStatusStopped string = "stopped" + InstanceStatusSuspended string = "suspended" + InstanceStatusTerminated string = "terminated" + InstanceStatusCreased string = "ceased" +) + +// Snapshot +// https://github.com/yunify/qingcloud-sdk-go/blob/c8f8d40dd4793219c129b7516d6f8ae130bc83c9/service/types.go#L2763 +// Available status values: pending, available, suspended, deleted, ceased +const ( + SnapshotStatusPending string = "pending" + SnapshotStatusAvailable string = "available" + SnapshotStatusSuspended string = "suspended" + SnapshotStatusDeleted string = "deleted" + SnapshotStatusCeased string = "ceased" +) + +// https://github.com/yunify/qingcloud-sdk-go/blob/c8f8d40dd4793219c129b7516d6f8ae130bc83c9/service/types.go#L2770 +// Available transition status values: creating, suspending, resuming, deleting, recovering +const ( + SnapshotTransitionStatusCreating string = "creating" + SnapshotTransitionStatusSuspending string = "suspending" + SnapshotTransitionStatusResuming string = "resuming" + SnapshotTransitionStatusDeleting string = "deleting" + SnapshotTransitionStatusRecovering string = "recovering" +) + +const ( + SnapshotFull int = 1 + SnapshotIncrement int = 0 +) + +// Volume +const ( + DiskStatusPending string = "pending" + DiskStatusAvailable string = "available" + DiskStatusInuse string = "in-use" + DiskStatusSuspended string = "suspended" + DiskStatusDeleted string = "deleted" + DiskStatusCeased string = "ceased" +) + +const ( + DiskSingleReplicaType int = 1 + DiskMultiReplicaType int = 2 + DefaultDiskReplicaType int = DiskMultiReplicaType +) + +var DiskReplicaTypeName = map[int]string{ + 1: "rpp-00000001", + 2: "rpp-00000002", +} + +// Zone +const ( + ZoneStatusActive = "active" + ZoneStatusFaulty = "faulty" + ZoneStatusDefunct = "defunct" +) diff --git a/pkg/server/instance/instance_manager_test.go b/pkg/cloudprovider/utils.go similarity index 53% rename from pkg/server/instance/instance_manager_test.go rename to pkg/cloudprovider/utils.go index ec135368..eefc6a52 100644 --- a/pkg/server/instance/instance_manager_test.go +++ b/pkg/cloudprovider/utils.go @@ -14,53 +14,21 @@ // | limitations under the License. // +------------------------------------------------------------------------- -package instance +package cloudprovider import ( - "testing" + qcconfig "github.com/yunify/qingcloud-sdk-go/config" ) -var getim = func() InstanceManager { - // get storage class - filePath := "/root/.qingcloud/config.yaml" - im, err := NewInstanceManagerFromFile(filePath) +// ReadConfigFromFile +// Read config file from a path and return config +func ReadConfigFromFile(filePath string) (*qcconfig.Config, error) { + config, err := qcconfig.NewDefault() if err != nil { - return nil + return nil, err } - - return im -} - -func TestFindInstance(t *testing.T) { - im := getim() - testcases := []struct { - name string - id string - found bool - }{ - { - name: "Available", - id: "fake", - found: true, - }, - { - name: "Not found", - id: "instance-1234", - found: false, - }, - { - name: "By name", - id: "neonsan-test", - found: false, - }, - } - for _, v := range testcases { - ins, err := im.FindInstance(v.id) - if err != nil { - t.Errorf("name %s error: %s", v.name, err.Error()) - } - if v.found && (ins == nil || *ins.InstanceID != v.id) { - t.Errorf("name %s: find id error", v.name) - } + if err = config.LoadConfigFromFilepath(filePath); err != nil { + return nil, err } + return config, nil } diff --git a/pkg/common/calculator.go b/pkg/common/calculator.go new file mode 100644 index 00000000..b1c3add0 --- /dev/null +++ b/pkg/common/calculator.go @@ -0,0 +1,30 @@ +package common + +import "github.com/container-storage-interface/spec/lib/go/csi" + +// GibToByte +// Convert GiB to Byte +func GibToByte(num int) int64 { + return int64(num) * Gib +} + +// ByteCeilToGib +// Convert Byte to Gib +func ByteCeilToGib(num int64) int { + if num <= 0 { + return 0 + } + res := num / Gib + if res*Gib < num { + res += 1 + } + return int(res) +} + +// Valid capacity bytes in capacity range +func IsValidCapacityBytes(cur int64, capRanges csi.CapacityRange) bool { + if cur < capRanges.GetRequiredBytes() || cur > capRanges.GetLimitBytes() { + return false + } + return true +} diff --git a/pkg/common/common.go b/pkg/common/common.go new file mode 100644 index 00000000..d136c262 --- /dev/null +++ b/pkg/common/common.go @@ -0,0 +1,17 @@ +package common + +import ( + "github.com/golang/glog" + "time" +) + +// EntryFunction print timestamps +// TODO: set log prefix, k8s.io/klog/klogr +func EntryFunction(functionName string) func() { + start := time.Now() + glog.Infof("*************** enter %s at %s ***************", functionName, start.String()) + return func() { + glog.Infof("=============== exit %s (%s since %s) ===============", functionName, time.Since(start), + start.String()) + } +} diff --git a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/server.go b/pkg/common/rpcserver.go similarity index 69% rename from vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/server.go rename to pkg/common/rpcserver.go index 9d3c9952..e948b953 100644 --- a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/server.go +++ b/pkg/common/rpcserver.go @@ -1,30 +1,16 @@ -/* -Copyright 2017 The Kubernetes 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 csicommon +package common import ( + "fmt" + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/golang/glog" + "github.com/kubernetes-csi/csi-lib-utils/protosanitizer" + "golang.org/x/net/context" + "google.golang.org/grpc" "net" "os" + "strings" "sync" - - "github.com/golang/glog" - "google.golang.org/grpc" - - "github.com/container-storage-interface/spec/lib/go/csi" ) // Defines Non blocking GRPC server interfaces @@ -110,3 +96,25 @@ func (s *nonBlockingGRPCServer) serve(endpoint string, ids csi.IdentityServer, c server.Serve(listener) } + +func logGRPC(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + glog.V(3).Infof("GRPC call: %s", info.FullMethod) + glog.V(5).Infof("GRPC request: %s", protosanitizer.StripSecrets(req)) + resp, err := handler(ctx, req) + if err != nil { + glog.Errorf("GRPC error: %v", err) + } else { + glog.V(5).Infof("GRPC response: %s", protosanitizer.StripSecrets(resp)) + } + return resp, err +} + +func ParseEndpoint(ep string) (string, string, error) { + if strings.HasPrefix(strings.ToLower(ep), "unix://") || strings.HasPrefix(strings.ToLower(ep), "tcp://") { + s := strings.SplitN(ep, "://", 2) + if s[1] != "" { + return s[0], s[1], nil + } + } + return "", "", fmt.Errorf("Invalid endpoint: %v", ep) +} diff --git a/pkg/common/types.go b/pkg/common/types.go new file mode 100644 index 00000000..27d857e4 --- /dev/null +++ b/pkg/common/types.go @@ -0,0 +1,19 @@ +package common + +const Int64Max = int64(^uint64(0) >> 1) + +const ( + Kib int64 = 1024 + Mib int64 = Kib * 1024 + Gib int64 = Mib * 1024 + Gib100 int64 = Gib * 100 + Tib int64 = Gib * 1024 + Tib100 int64 = Tib * 100 +) + +const ( + FileSystemExt3 string = "ext3" + FileSystemExt4 string = "ext4" + FileSystemXfs string = "xfs" + DefaultFileSystem string = FileSystemExt4 +) diff --git a/pkg/disk/disk.go b/pkg/disk/disk.go deleted file mode 100644 index 69782acd..00000000 --- a/pkg/disk/disk.go +++ /dev/null @@ -1,102 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 disk - -import ( - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/golang/glog" - "github.com/kubernetes-csi/drivers/pkg/csi-common" - "github.com/yunify/qingcloud-csi/pkg/server" -) - -const version = "v1.1.0" - -type disk struct { - driver *csicommon.CSIDriver - - ids *identityServer - ns *nodeServer - cs *controllerServer - - cap []*csi.VolumeCapability_AccessMode - cscap []*csi.ControllerServiceCapability - - cloud *server.ServerConfig -} - -// GetDiskDriver -// Create disk driver -func GetDiskDriver() *disk { - return &disk{} -} - -// NewIdentityServer -// Create identity server -func NewIdentityServer(d *csicommon.CSIDriver, svr *server.ServerConfig) *identityServer { - return &identityServer{ - DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d), - cloudServer: svr, - } -} - -// NewControllerServer -// Create controller server -func NewControllerServer(d *csicommon.CSIDriver, svr *server.ServerConfig) *controllerServer { - return &controllerServer{ - DefaultControllerServer: csicommon.NewDefaultControllerServer(d), - cloudServer: svr, - } -} - -// NewNodeServer -// Create node server -func NewNodeServer(d *csicommon.CSIDriver, svr *server.ServerConfig) *nodeServer { - return &nodeServer{ - DefaultNodeServer: csicommon.NewDefaultNodeServer(d), - cloudServer: svr, - } -} - -// Run -// Initial and start CSI driver -func (d *disk) Run(driverName, nodeID, endpoint string, serverConfig *server.ServerConfig) { - glog.Infof("Driver: %v version: %v", driverName, version) - - // Initialize default library driver - d.driver = csicommon.NewCSIDriver(driverName, version, nodeID) - if d.driver == nil { - glog.Fatalln("Failed to initialize CSI Driver.") - } - - d.driver.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{ - csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, - csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, - csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT, - }) - d.driver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{ - csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}) - - // Create GRPC servers - d.ids = NewIdentityServer(d.driver, serverConfig) - d.ns = NewNodeServer(d.driver, serverConfig) - d.cs = NewControllerServer(d.driver, serverConfig) - - s := csicommon.NewNonBlockingGRPCServer() - s.Start(endpoint, d.ids, d.cs, d.ns) - s.Wait() - -} diff --git a/pkg/disk/driver/driver.go b/pkg/disk/driver/driver.go new file mode 100644 index 00000000..ac260d6b --- /dev/null +++ b/pkg/disk/driver/driver.go @@ -0,0 +1,178 @@ +// +------------------------------------------------------------------------- +// | Copyright (C) 2018 Yunify, Inc. +// +------------------------------------------------------------------------- +// | Licensed under the Apache License, Version 2.0 (the "License"); +// | you may not use this work except in compliance with the License. +// | You may obtain a copy of the License in the LICENSE file, or 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 driver + +import ( + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/golang/glog" +) + +type DiskDriver struct { + name string + version string + nodeId string + maxVolume int64 + volumeCap []*csi.VolumeCapability_AccessMode + controllerCap []*csi.ControllerServiceCapability + nodeCap []*csi.NodeServiceCapability + pluginCap []*csi.PluginCapability +} + +type InitDiskDriverInput struct { + Name string + Version string + NodeId string + MaxVolume int64 + VolumeCap []csi.VolumeCapability_AccessMode_Mode + ControllerCap []csi.ControllerServiceCapability_RPC_Type + NodeCap []csi.NodeServiceCapability_RPC_Type + PluginCap []*csi.PluginCapability +} + +// GetDiskDriver +// Create disk driver +func GetDiskDriver() *DiskDriver { + return &DiskDriver{} +} + +func (d *DiskDriver) InitDiskDriver(input *InitDiskDriverInput) { + d.name = input.Name + d.version = input.Version + // Setup Node Id + d.nodeId = input.NodeId + // Setup max volume + d.maxVolume = input.MaxVolume + // Setup cap + d.addVolumeCapabilityAccessModes(input.VolumeCap) + d.addControllerServiceCapabilities(input.ControllerCap) + d.addNodeServiceCapabilities(input.NodeCap) + d.addPluginCapabilities(input.PluginCap) +} + +func (d *DiskDriver) addVolumeCapabilityAccessModes(vc []csi.VolumeCapability_AccessMode_Mode) { + var vca []*csi.VolumeCapability_AccessMode + for _, c := range vc { + glog.V(4).Infof("Enabling volume access mode: %v", c.String()) + vca = append(vca, NewVolumeCapabilityAccessMode(c)) + } + d.volumeCap = vca +} + +func (d *DiskDriver) addControllerServiceCapabilities(cl []csi.ControllerServiceCapability_RPC_Type) { + var csc []*csi.ControllerServiceCapability + for _, c := range cl { + glog.V(4).Infof("Enabling controller service capability: %v", c.String()) + csc = append(csc, NewControllerServiceCapability(c)) + } + d.controllerCap = csc +} + +func (d *DiskDriver) addNodeServiceCapabilities(nl []csi.NodeServiceCapability_RPC_Type) { + var nsc []*csi.NodeServiceCapability + for _, n := range nl { + glog.V(4).Infof("Enabling node service capability: %v", n.String()) + nsc = append(nsc, NewNodeServiceCapability(n)) + } + d.nodeCap = nsc +} + +func (d *DiskDriver) addPluginCapabilities(cap []*csi.PluginCapability) { + d.pluginCap = cap +} + +func (d *DiskDriver) ValidateControllerServiceRequest(c csi.ControllerServiceCapability_RPC_Type) bool { + if c == csi.ControllerServiceCapability_RPC_UNKNOWN { + return true + } + + for _, cap := range d.controllerCap { + if c == cap.GetRpc().Type { + return true + } + } + return false +} + +func (d *DiskDriver) ValidateNodeServiceRequest(c csi.NodeServiceCapability_RPC_Type) bool { + if c == csi.NodeServiceCapability_RPC_UNKNOWN { + return true + } + for _, cap := range d.nodeCap { + if c == cap.GetRpc().Type { + return true + } + } + return false + +} + +func (d *DiskDriver) ValidateVolumeCapability(cap *csi.VolumeCapability) bool { + if !d.ValidateVolumeAccessMode(cap.GetAccessMode().GetMode()) { + return false + } + return true +} + +func (d *DiskDriver) ValidateVolumeCapabilities(caps []*csi.VolumeCapability) bool { + for _, cap := range caps { + if !d.ValidateVolumeAccessMode(cap.GetAccessMode().GetMode()) { + return false + } + } + return true +} + +func (d *DiskDriver) ValidateVolumeAccessMode(c csi.VolumeCapability_AccessMode_Mode) bool { + for _, mode := range d.volumeCap { + if c == mode.GetMode() { + return true + } + } + return false +} + +func (d *DiskDriver) GetName() string { + return d.name +} + +func (d *DiskDriver) GetVersion() string { + return d.version +} + +func (d *DiskDriver) GetInstanceId() string { + return d.nodeId +} + +func (d *DiskDriver) GetMaxVolumePerNode() int64 { + return d.maxVolume +} + +func (d *DiskDriver) GetControllerCapability() []*csi.ControllerServiceCapability { + return d.controllerCap +} + +func (d *DiskDriver) GetNodeCapability() []*csi.NodeServiceCapability { + return d.nodeCap +} + +func (d *DiskDriver) GetPluginCapability() []*csi.PluginCapability { + return d.pluginCap +} + +func (d *DiskDriver) GetVolumeCapability() []*csi.VolumeCapability_AccessMode { + return d.volumeCap +} diff --git a/pkg/disk/driver/storageclass.go b/pkg/disk/driver/storageclass.go new file mode 100644 index 00000000..83a817cb --- /dev/null +++ b/pkg/disk/driver/storageclass.go @@ -0,0 +1,151 @@ +package driver + +import ( + "fmt" + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/yunify/qingcloud-csi/pkg/cloudprovider" + "github.com/yunify/qingcloud-csi/pkg/common" + "strconv" +) + +type QingStorageClass struct { + DiskType int `json:"type"` + MaxSize int `json:"maxSize"` + MinSize int `json:"minSize"` + StepSize int `json:"stepSize"` + FsType string `json:"fsType"` + Replica int `json:"replica"` +} + +// NewDefaultQingStorageClass create default qingStorageClass object +func NewDefaultQingStorageClass() *QingStorageClass { + return NewDefaultQingStorageClassFromType(SSDEnterpriseDiskType) +} + +// NewDefaultQingStorageClassFromType create default qingStorageClass by specified volume type +func NewDefaultQingStorageClassFromType(diskType int) *QingStorageClass { + if IsValidDiskType(diskType) != true { + return nil + } + return &QingStorageClass{ + DiskType: diskType, + MaxSize: VolumeTypeToMaxSize[diskType], + MinSize: VolumeTypeToMinSize[diskType], + StepSize: VolumeTypeToStepSize[diskType], + FsType: common.DefaultFileSystem, + Replica: cloudprovider.DefaultDiskReplicaType, + } +} + +// NewQingStorageClassFromMap create qingStorageClass object from map +func NewQingStorageClassFromMap(opt map[string]string) (*QingStorageClass, error) { + sVolType, volTypeOk := opt["type"] + sMaxSize, maxSizeOk := opt["maxSize"] + sMinSize, minSizeOk := opt["minSize"] + sStepSize, stepSizeOk := opt["stepSize"] + sFsType, fsTypeOk := opt["fsType"] + sReplica, replicaOk := opt["replica"] + if volTypeOk == false { + return NewDefaultQingStorageClass(), nil + } + // Convert volume type to integer + iVolType, err := strconv.Atoi(sVolType) + if err != nil { + return nil, err + } + sc := NewDefaultQingStorageClassFromType(iVolType) + if maxSizeOk == true && minSizeOk == true && stepSizeOk == true { + // Get volume max size + iMaxSize, err := strconv.Atoi(sMaxSize) + if err != nil { + return nil, err + } + if iMaxSize <= 0 { + return nil, fmt.Errorf("max size must greater than zero") + } + sc.MaxSize = iMaxSize + // Get volume min size + iMinSize, err := strconv.Atoi(sMinSize) + if err != nil { + return nil, err + } + if iMinSize <= 0 { + return nil, fmt.Errorf("min size must greater than zero") + } + sc.MinSize = iMinSize + // Ensure volume minSize less than volume maxSize + if sc.MaxSize < sc.MinSize { + return nil, fmt.Errorf("max size must greater than or equal to min size") + } + // Get volume step size + iStepSize, err := strconv.Atoi(sStepSize) + if err != nil { + return nil, err + } + if iStepSize <= 0 { + return nil, fmt.Errorf("step size must greater than zero") + } + sc.StepSize = iStepSize + } + + if fsTypeOk == true { + if !IsValidFileSystemType(sFsType) { + return nil, fmt.Errorf("unsupported filesystem type %s", sFsType) + } + sc.FsType = sFsType + } + + // Get volume replicas + if replicaOk == true { + iReplica, err := strconv.Atoi(sReplica) + if err != nil { + return nil, err + } + if !IsValidReplica(iReplica) { + return nil, fmt.Errorf("unsupported replica %s", sReplica) + } + sc.Replica = iReplica + } + + return sc, nil +} + +func (sc QingStorageClass) GetMinSizeByte() int64 { + return int64(sc.MinSize) * common.Gib +} + +func (sc QingStorageClass) GetMaxSizeByte() int64 { + return int64(sc.MaxSize) * common.Gib +} +func (sc QingStorageClass) GetStepSizeByte() int64 { + return int64(sc.StepSize) * common.Gib +} + +// FormatVolumeSize transfer to proper volume size +func (sc QingStorageClass) FormatVolumeSizeByte(sizeByte int64) int64 { + if sizeByte <= sc.GetMinSizeByte() { + return sc.GetMinSizeByte() + } else if sizeByte > sc.GetMaxSizeByte() { + return sc.GetMaxSizeByte() + } + if sizeByte%sc.GetStepSizeByte() != 0 { + sizeByte = (sizeByte/sc.GetStepSizeByte() + 1) * sc.GetStepSizeByte() + } + if sizeByte > sc.GetMaxSizeByte() { + return sc.GetMaxSizeByte() + } + return sizeByte +} + +// Required Volume Size +func (sc QingStorageClass) GetRequiredVolumeSize(capRange csi.CapacityRange) (int64, error) { + res := int64(0) + if capRange.GetRequiredBytes() > 0 { + res = capRange.GetRequiredBytes() + } + res = sc.FormatVolumeSizeByte(res) + if capRange.GetLimitBytes() > 0 && res > capRange.GetLimitBytes() { + return -1, fmt.Errorf("volume required bytes %d greater than limit bytes %d", res, capRange.GetLimitBytes()) + } + return res, nil +} diff --git a/pkg/disk/driver/types.go b/pkg/disk/driver/types.go new file mode 100644 index 00000000..80a772ee --- /dev/null +++ b/pkg/disk/driver/types.go @@ -0,0 +1,91 @@ +package driver + +import "github.com/container-storage-interface/spec/lib/go/csi" + +const ( + DefaultInstanceIdFilePath = "/etc/qingcloud/instance-id" +) + +var DefaultVolumeAccessModeType = []csi.VolumeCapability_AccessMode_Mode{ + csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, +} +var DefaultControllerServiceCapability = []csi.ControllerServiceCapability_RPC_Type{ + csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT, + csi.ControllerServiceCapability_RPC_EXPAND_VOLUME, +} +var DefaultNodeServiceCapability = []csi.NodeServiceCapability_RPC_Type{ + csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, + csi.NodeServiceCapability_RPC_EXPAND_VOLUME, +} +var DefaultPluginCapability = []*csi.PluginCapability{ + { + Type: &csi.PluginCapability_Service_{ + Service: &csi.PluginCapability_Service{ + Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, + }, + }, + }, + { + Type: &csi.PluginCapability_VolumeExpansion_{ + VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ + Type: csi.PluginCapability_VolumeExpansion_OFFLINE, + }, + }, + }, + { + Type: &csi.PluginCapability_VolumeExpansion_{ + VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ + Type: csi.PluginCapability_VolumeExpansion_ONLINE, + }, + }, + }, +} + +const ( + HighPerformanceDiskType int = 0 + HighCapacityDiskType int = 2 + SuperHighPerformanceDiskType int = 3 + StandardDiskType int = 100 + SSDEnterpriseDiskType int = 200 + NeonSANDiskType int = 5 +) + +// convert volume type to string +// https://docs.qingcloud.com/product/api/action/volume/create_volumes.html +var VolumeTypeName = map[int]string{ + 0: "HighPerformance", + 2: "HighCapacity", + 3: "SuperHighPerformance", + 100: "Standard", + 200: "SSDEnterprise", + 5: "NeonSAN", +} + +var VolumeTypeToStepSize = map[int]int{ + 0: 10, + 2: 50, + 3: 10, + 100: 10, + 200: 10, + 5: 100, +} + +var VolumeTypeToMinSize = map[int]int{ + 0: 10, + 2: 100, + 3: 10, + 100: 10, + 200: 10, + 5: 100, +} + +var VolumeTypeToMaxSize = map[int]int{ + 0: 2000, + 2: 5000, + 3: 2000, + 100: 2000, + 200: 2000, + 5: 50000, +} diff --git a/pkg/disk/driver/utils.go b/pkg/disk/driver/utils.go new file mode 100644 index 00000000..7f311e34 --- /dev/null +++ b/pkg/disk/driver/utils.go @@ -0,0 +1,164 @@ +package driver + +import ( + "fmt" + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/golang/glog" + "github.com/yunify/qingcloud-csi/pkg/cloudprovider" + "github.com/yunify/qingcloud-csi/pkg/common" + "io/ioutil" + "strings" +) + +// Check replica +// Support: 2 MultiReplicas, 1 SingleReplica +func IsValidReplica(replica int) bool { + switch replica { + case cloudprovider.DiskMultiReplicaType: + return true + case cloudprovider.DiskSingleReplicaType: + return true + default: + return false + } +} + +// Check file system type +// Support: ext3, ext4 and xfs +func IsValidFileSystemType(fs string) bool { + switch fs { + case common.FileSystemExt3: + return true + case common.FileSystemExt4: + return true + case common.FileSystemXfs: + return true + default: + return false + } +} + +// Check disk type +func IsValidDiskType(volumeType int) bool { + if _, ok := VolumeTypeName[volumeType]; ok { + return true + } + return false +} + +// FormatVolumeSize transfer to proper volume size +func FormatVolumeSize(volType int, volSize int) int { + _, ok := VolumeTypeName[volType] + if ok == false { + return -1 + } + volTypeMinSize := VolumeTypeToMinSize[volType] + volTypeMaxSize := VolumeTypeToMaxSize[volType] + volTypeStepSize := VolumeTypeToStepSize[volType] + if volSize <= volTypeMinSize { + return volTypeMinSize + } else if volSize >= volTypeMaxSize { + return volTypeMaxSize + } + if volSize%volTypeStepSize != 0 { + volSize = (volSize/volTypeStepSize + 1) * volTypeStepSize + } + if volSize >= volTypeMaxSize { + return volTypeMaxSize + } + return volSize +} + +func GetInstanceIdFromFile(filepath string) (instanceId string, err error) { + bytes, err := ioutil.ReadFile(filepath) + if err != nil { + return "", err + } + instanceId = string(bytes[:]) + instanceId = strings.Replace(instanceId, "\n", "", -1) + glog.Infof("Getting instance-id: \"%s\"", instanceId) + return instanceId, nil +} + +func NewVolumeCapabilityAccessMode(mode csi.VolumeCapability_AccessMode_Mode) *csi.VolumeCapability_AccessMode { + return &csi.VolumeCapability_AccessMode{Mode: mode} +} + +func NewControllerServiceCapability(cap csi.ControllerServiceCapability_RPC_Type) *csi.ControllerServiceCapability { + return &csi.ControllerServiceCapability{ + Type: &csi.ControllerServiceCapability_Rpc{ + Rpc: &csi.ControllerServiceCapability_RPC{ + Type: cap, + }, + }, + } +} + +func NewNodeServiceCapability(cap csi.NodeServiceCapability_RPC_Type) *csi.NodeServiceCapability { + return &csi.NodeServiceCapability{ + Type: &csi.NodeServiceCapability_Rpc{ + Rpc: &csi.NodeServiceCapability_RPC{ + Type: cap, + }, + }, + } +} + +func validateVolumeCapabilities(vcs []*csi.VolumeCapability) error { + isMnt := false + isBlk := false + + if vcs == nil { + return fmt.Errorf("volume capabilities is nil") + } + + for _, vc := range vcs { + if err := validateVolumeCapability(vc); err != nil { + return err + } + if blk := vc.GetBlock(); blk != nil { + isBlk = true + } + if mnt := vc.GetMount(); mnt != nil { + isMnt = true + } + } + + if isBlk && isMnt { + return fmt.Errorf("both mount and block volume capabilities specified") + } + + return nil +} + +func validateVolumeCapability(vc *csi.VolumeCapability) error { + if err := validateAccessMode(vc.GetAccessMode()); err != nil { + return err + } + blk := vc.GetBlock() + mnt := vc.GetMount() + if mnt == nil && blk == nil { + return fmt.Errorf("must specify an access type") + } + if mnt != nil && blk != nil { + return fmt.Errorf("specified both mount and block access types") + } + return nil +} + +func validateAccessMode(am *csi.VolumeCapability_AccessMode) error { + if am == nil { + return fmt.Errorf("access mode is nil") + } + + switch am.GetMode() { + case csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER: + case csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY: + case csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY: + case csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER: + return fmt.Errorf("MULTI_NODE_MULTI_WRITER access mode is not yet supported for PD") + default: + return fmt.Errorf("%v access mode is not supported for for PD", am.GetMode()) + } + return nil +} diff --git a/pkg/disk/controllerserver.go b/pkg/disk/rpcserver/controllerserver.go similarity index 67% rename from pkg/disk/controllerserver.go rename to pkg/disk/rpcserver/controllerserver.go index 27d66e89..11110450 100644 --- a/pkg/disk/controllerserver.go +++ b/pkg/disk/rpcserver/controllerserver.go @@ -14,7 +14,7 @@ // | limitations under the License. // +------------------------------------------------------------------------- -package disk +package rpcserver import ( "fmt" @@ -22,22 +22,29 @@ import ( "github.com/golang/glog" "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/timestamp" - "github.com/kubernetes-csi/drivers/pkg/csi-common" - "github.com/yunify/qingcloud-csi/pkg/server" - "github.com/yunify/qingcloud-csi/pkg/server/instance" - "github.com/yunify/qingcloud-csi/pkg/server/snapshot" - "github.com/yunify/qingcloud-csi/pkg/server/storageclass" - "github.com/yunify/qingcloud-csi/pkg/server/volume" + "github.com/yunify/qingcloud-csi/pkg/cloudprovider" + "github.com/yunify/qingcloud-csi/pkg/common" + "github.com/yunify/qingcloud-csi/pkg/disk/driver" "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "math" "strings" "time" ) -type controllerServer struct { - *csicommon.DefaultControllerServer - cloudServer *server.ServerConfig +type DiskControllerServer struct { + driver *driver.DiskDriver + cloud cloudprovider.CloudManager +} + +// NewControllerServer +// Create controller server +func NewControllerServer(d *driver.DiskDriver, c cloudprovider.CloudManager) *DiskControllerServer { + return &DiskControllerServer{ + driver: d, + cloud: c, + } } // This operation MUST be idempotent @@ -47,18 +54,18 @@ type controllerServer struct { // 3. Clone volume: CREATE_DELETE_VOLUME and CLONE_VOLUME // csi.CreateVolumeRequest: name +Required // capability +Required -func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { +func (cs *DiskControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { glog.Info("----- Start CreateVolume -----") defer glog.Info("===== End CreateVolume =====") // 0. Prepare - if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil { - glog.Errorf("Invalid create volume req: %v", req) - return nil, err + if isValid := cs.driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); isValid != true { + // TODO + return nil, status.Error(codes.PermissionDenied, "") } // Required volume capability - if req.VolumeCapabilities == nil { + if req.GetVolumeCapabilities() == nil { return nil, status.Error(codes.InvalidArgument, "Volume capabilities missing in request") - } else if !server.ContainsVolumeCapabilities(cs.Driver.GetVolumeCapabilityAccessModes(), req.GetVolumeCapabilities()) { + } else if !cs.driver.ValidateVolumeCapabilities(req.GetVolumeCapabilities()) { return nil, status.Error(codes.InvalidArgument, "Volume capabilities not match") } // Check sanity of request Name, Volume Capabilities @@ -67,49 +74,51 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } volumeName := req.GetName() - // create VolumeManager object - vm, err := volume.NewVolumeManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) + // Get Volume Capacity + capRange := csi.CapacityRange{ + RequiredBytes: 0, + LimitBytes: math.MaxInt64, + } + if req.GetCapacityRange() != nil { + if req.GetCapacityRange().GetRequiredBytes() > 0 { + capRange.RequiredBytes = req.GetCapacityRange().GetRequiredBytes() + } + if req.GetCapacityRange().GetLimitBytes() > 0 { + capRange.LimitBytes = req.GetCapacityRange().GetLimitBytes() + } } + // create StorageClass object - sc, err := storageclass.NewQingStorageClassFromMap(req.GetParameters()) + sc, err := driver.NewQingStorageClassFromMap(req.GetParameters()) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } // get request volume capacity range - requiredByte := req.GetCapacityRange().GetRequiredBytes() - requiredGib := sc.FormatVolumeSize(server.ByteCeilToGib(requiredByte), sc.VolumeStepSize) - limitByte := req.GetCapacityRange().GetLimitBytes() - if limitByte == 0 { - limitByte = server.Int64Max - } - // check volume range - if server.GibToByte(requiredGib) < requiredByte || server.GibToByte(requiredGib) > limitByte || - requiredGib < sc.VolumeMinSize || requiredGib > sc.VolumeMaxSize { - glog.Errorf("Request capacity range [%d, %d] bytes, storage class capacity range [%d, %d] GB, format required size: %d gb", - requiredByte, limitByte, sc.VolumeMinSize, sc.VolumeMaxSize, requiredGib) - return nil, status.Error(codes.OutOfRange, "Unsupport capacity range") + requiredSizeByte, err := sc.GetRequiredVolumeSize(capRange) + if err != nil { + return nil, status.Errorf(codes.OutOfRange, "unsupported capacity range, error: %s", err.Error()) } // should not fail when requesting to create a volume with already existing name and same capacity // should fail when requesting to create a volume with already existing name and different capacity. - exVol, err := vm.FindVolumeByName(volumeName) + exVol, err := cs.cloud.FindVolumeByName(volumeName) if err != nil { return nil, status.Error(codes.Internal, fmt.Sprintf("Find volume by name error: %s, %s", volumeName, err.Error())) } if exVol != nil { glog.Infof("Request volume name: %s, capacity range [%d,%d] bytes, type: %d, zone: %s", - volumeName, requiredByte, limitByte, sc.VolumeType, vm.GetZone()) + volumeName, capRange.GetRequiredBytes(), capRange.GetLimitBytes(), sc.DiskType, + cs.cloud.GetZone()) glog.Infof("Exist volume name: %s, id: %s, capacity: %d bytes, type: %d, zone: %s", - *exVol.VolumeName, *exVol.VolumeID, server.GibToByte(*exVol.Size), *exVol.VolumeType, vm.GetZone()) - if *exVol.Size >= requiredGib && int64(*exVol.Size)*server.Gib <= limitByte && *exVol.VolumeType == sc. - VolumeType { + *exVol.VolumeName, *exVol.VolumeID, common.GibToByte(*exVol.Size), *exVol.VolumeType, cs.cloud.GetZone()) + exVolSizeByte := common.GibToByte(*exVol.Size) + if common.IsValidCapacityBytes(exVolSizeByte, capRange) && + *exVol.VolumeType == sc.DiskType { // existing volume is compatible with new request and should be reused. return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ VolumeId: *exVol.VolumeID, - CapacityBytes: int64(*exVol.Size) * server.Gib, + CapacityBytes: exVolSizeByte, VolumeContext: req.GetParameters(), }, }, nil @@ -122,8 +131,10 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol volContSrc := req.GetVolumeContentSource() if volContSrc == nil { // create a empty volume - glog.Infof("Creating empty volume %s with %d GB in zone %s...", volumeName, requiredGib, vm.GetZone()) - volumeId, err := vm.CreateVolume(volumeName, requiredGib, *sc) + requiredSizeGib := common.ByteCeilToGib(requiredSizeByte) + glog.Infof("Creating empty volume %s with %d Gib in zone %s...", volumeName, requiredSizeGib, + cs.cloud.GetZone()) + volumeId, err := cs.cloud.CreateVolume(volumeName, requiredSizeGib, sc.Replica, sc.DiskType) if err != nil { return nil, err } @@ -131,16 +142,17 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ VolumeId: volumeId, - CapacityBytes: int64(requiredGib) * server.Gib, + CapacityBytes: requiredSizeByte, VolumeContext: req.GetParameters(), }, }, nil } else { if volContSrc.GetSnapshot() != nil { // Get capability - if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); err != nil { - glog.Errorf("Invalid create volume req: %v", req) - return nil, err + if isValid := cs.driver.ValidateControllerServiceRequest(csi. + ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); isValid != true { + // TODO + return nil, status.Error(codes.PermissionDenied, "") } // Get snapshot id if len(volContSrc.GetSnapshot().GetSnapshotId()) == 0 { @@ -148,13 +160,8 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } snapId := volContSrc.GetSnapshot().GetSnapshotId() - // create SnapshotManager object - sm, err := snapshot.NewSnapshotManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } // Find snapshot before restore volume from snapshot - snapInfo, err := sm.FindSnapshot(snapId) + snapInfo, err := cs.cloud.FindSnapshot(snapId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -163,25 +170,25 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } // Compare snapshot required volume size - requiredRestoreVolumeSizeInBytes := int64(*snapInfo.Size) * server.Mib - if !server.IsValidCapacityBytes(requiredRestoreVolumeSizeInBytes, []int64{requiredByte}, []int64{limitByte}) { + requiredRestoreVolumeSizeInBytes := int64(*snapInfo.Size) * common.Mib + if !common.IsValidCapacityBytes(requiredRestoreVolumeSizeInBytes, capRange) { glog.Errorf("Restore volume request size [%d], out of the request range [%d, %d] bytes", - requiredRestoreVolumeSizeInBytes, requiredByte, limitByte) + requiredRestoreVolumeSizeInBytes, capRange.GetRequiredBytes(), capRange.GetLimitBytes()) return nil, status.Error(codes.OutOfRange, "Unsupport capacity range") } // restore volume from snapshot glog.Infof("Restore volume name [%s] from snapshot id [%s] in zone [%s].", - volumeName, snapId, vm.GetZone()) - volId, err := vm.CreateVolumeFromSnapshot(volumeName, snapId) + volumeName, snapId, cs.cloud.GetZone()) + volId, err := cs.cloud.CreateVolumeFromSnapshot(volumeName, snapId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } // Find volume - exVol, err := vm.FindVolume(volId) + exVol, err := cs.cloud.FindVolume(volId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - actualRestoreVolumeSizeInBytes := int64(*exVol.Size) * server.Gib + actualRestoreVolumeSizeInBytes := int64(*exVol.Size) * common.Gib if actualRestoreVolumeSizeInBytes != requiredRestoreVolumeSizeInBytes { return nil, status.Error(codes.Internal, fmt.Sprintf("expected volume size [%d], but actually [%d], volume id [%s], snapshot id [%s]", @@ -197,9 +204,10 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } else if volContSrc.GetVolume() != nil { // clone volume // Get capability - if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CLONE_VOLUME); err != nil { + if isValid := cs.driver.ValidateControllerServiceRequest(csi. + ControllerServiceCapability_RPC_CLONE_VOLUME); isValid != true { glog.Errorf("Invalid create volume req: %v", req) - return nil, err + return nil, status.Error(codes.PermissionDenied, "") } } } @@ -208,12 +216,13 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol // This operation MUST be idempotent // volume id is REQUIRED in csi.DeleteVolumeRequest -func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { +func (cs *DiskControllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { glog.Info("----- Start DeleteVolume -----") defer glog.Info("===== End DeleteVolume =====") - if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil { + if isValid := cs.driver.ValidateControllerServiceRequest(csi. + ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); isValid != true { glog.Errorf("invalid delete volume req: %v", req) - return nil, err + return nil, status.Error(codes.PermissionDenied, "") } // Check sanity of request Name, Volume Capabilities if len(req.GetVolumeId()) == 0 { @@ -224,14 +233,10 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol // Deleting disk glog.Infof("deleting volume %s", volumeId) - // Create VolumeManager object - vm, err := volume.NewVolumeManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } + // For idempotent: // MUST reply OK when volume does not exist - volInfo, err := vm.FindVolume(volumeId) + volInfo, err := cs.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -239,19 +244,19 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol return &csi.DeleteVolumeResponse{}, nil } // Is volume in use - if *volInfo.Status == volume.DiskStatusInuse { + if *volInfo.Status == cloudprovider.DiskStatusInuse { return nil, status.Errorf(codes.FailedPrecondition, "volume is in use by another resource") } // Do delete volume - glog.Infof("Deleting volume %s status %s in zone %s...", volumeId, *volInfo.Status, vm.GetZone()) + glog.Infof("Deleting volume %s status %s in zone %s...", volumeId, *volInfo.Status, cs.cloud.GetZone()) // When return with retry message at deleting volume, retry after several seconds. // Retry times is 10. // Retry interval is changed from 1 second to 10 seconds. for i := 1; i <= 10; i++ { - err = vm.DeleteVolume(volumeId) + err = cs.cloud.DeleteVolume(volumeId) if err != nil { - glog.Infof("Failed to delete disk volume: %s in %s with error: %v", volumeId, vm.GetZone(), err) - if strings.Contains(err.Error(), server.RetryString) { + glog.Infof("Failed to delete disk volume: %s in %s with error: %v", volumeId, cs.cloud.GetZone(), err) + if strings.Contains(err.Error(), cloudprovider.RetryString) { time.Sleep(time.Duration(i*2) * time.Second) } else { return nil, status.Error(codes.Internal, err.Error()) @@ -267,12 +272,14 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol // node id + Required // volume capability + Required // readonly + Required (This field is NOT provided when requesting in Kubernetes) -func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { +func (cs *DiskControllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi. + ControllerPublishVolumeResponse, error) { glog.Info("----- Start ControllerPublishVolume -----") defer glog.Info("===== End ControllerPublishVolume =====") - if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME); err != nil { - glog.Errorf("invalid publish volume req: %v", req) - return nil, err + if isValid := cs.driver.ValidateControllerServiceRequest(csi. + ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); isValid != true { + glog.Errorf("invalid delete volume req: %v", req) + return nil, status.Error(codes.PermissionDenied, "") } // 0. Preflight // check volume id arguments @@ -288,20 +295,9 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs return nil, status.Error(codes.InvalidArgument, "No volume capability is provided ") } - // create volume manager object - vm, err := volume.NewVolumeManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - // create instance manager object - im, err := instance.NewInstanceManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - // if volume id not exist volumeId := req.GetVolumeId() - exVol, err := vm.FindVolume(volumeId) + exVol, err := cs.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -311,7 +307,7 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs // if instance id not exist nodeId := req.GetNodeId() - exIns, err := im.FindInstance(nodeId) + exIns, err := cs.cloud.FindInstance(nodeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -329,8 +325,8 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs } // 1. Attach // attach volume - glog.Infof("Attaching volume %s to instance %s in zone %s...", volumeId, nodeId, vm.GetZone()) - err = vm.AttachVolume(volumeId, nodeId) + glog.Infof("Attaching volume %s to instance %s in zone %s...", volumeId, nodeId, cs.cloud.GetZone()) + err = cs.cloud.AttachVolume(volumeId, nodeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -339,7 +335,7 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs // Retry times is 3. // Retry interval is changed from 1 second to 3 seconds. for i := 1; i <= 3; i++ { - volInfo, err := vm.FindVolume(volumeId) + volInfo, err := cs.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -357,7 +353,7 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs // Cannot find device path // Try to detach volume glog.Infof("Cannot find device path and going to detach volume %s", volumeId) - if err := vm.DetachVolume(volumeId, nodeId); err != nil { + if err := cs.cloud.DetachVolume(volumeId, nodeId); err != nil { return nil, status.Errorf(codes.Internal, "cannot find device path, detach volume %s failed", volumeId) } else { @@ -369,12 +365,14 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs // This operation MUST be idempotent // csi.ControllerUnpublishVolumeRequest: volume id +Required -func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { +func (cs *DiskControllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi. + ControllerUnpublishVolumeResponse, error) { glog.Info("----- Start ControllerUnpublishVolume -----") defer glog.Info("===== End ControllerUnpublishVolume =====") - if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME); err != nil { + if isValid := cs.driver.ValidateControllerServiceRequest(csi. + ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME); isValid != true { glog.Errorf("invalid unpublish volume req: %v", req) - return nil, err + return nil, status.Error(codes.PermissionDenied, "") } // 0. Preflight // check arguments @@ -385,19 +383,9 @@ func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req * nodeId := req.GetNodeId() // 1. Detach - // create volume provisioner object - vm, err := volume.NewVolumeManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - // create instance manager object - im, err := instance.NewInstanceManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } // check volume exist - exVol, err := vm.FindVolume(volumeId) + exVol, err := cs.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -406,7 +394,7 @@ func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req * } // check node exist - exIns, err := im.FindInstance(nodeId) + exIns, err := cs.cloud.FindInstance(nodeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -415,8 +403,8 @@ func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req * } // do detach - glog.Infof("Detaching volume %s to instance %s in zone %s...", volumeId, nodeId, vm.GetZone()) - err = vm.DetachVolume(volumeId, nodeId) + glog.Infof("Detaching volume %s to instance %s in zone %s...", volumeId, nodeId, cs.cloud.GetZone()) + err = cs.cloud.DetachVolume(volumeId, nodeId) if err != nil { glog.Errorf("failed to detach disk image: %s from instance %s with error: %v", volumeId, nodeId, err) @@ -429,7 +417,8 @@ func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req * // This operation MUST be idempotent // csi.ValidateVolumeCapabilitiesRequest: volume id + Required // volume capability + Required -func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { +func (cs *DiskControllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi. + ValidateVolumeCapabilitiesResponse, error) { glog.Info("----- Start ValidateVolumeCapabilities -----") defer glog.Info("===== End ValidateVolumeCapabilities =====") @@ -444,58 +433,48 @@ func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req } // check volume exist - vm, err := volume.NewVolumeManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } volumeId := req.GetVolumeId() - vol, err := vm.FindVolume(volumeId) + vol, err := cs.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } if vol == nil { - return nil, status.Errorf(codes.NotFound, "Volume %s does not exist", volumeId) + return nil, status.Errorf(codes.NotFound, "volume %s does not exist", volumeId) } // check capability for _, c := range req.GetVolumeCapabilities() { found := false - for _, c1 := range cs.Driver.GetVolumeCapabilityAccessModes() { + for _, c1 := range cs.driver.GetVolumeCapability() { if c1.GetMode() == c.GetAccessMode().GetMode() { found = true } } if !found { return &csi.ValidateVolumeCapabilitiesResponse{ - Message: "Driver does not support mode:" + c.GetAccessMode().GetMode().String(), - }, nil + Message: "Driver doesnot support mode:" + c.GetAccessMode().GetMode().String(), + }, status.Error(codes.InvalidArgument, "Driver doesnot support mode:"+c.GetAccessMode().GetMode().String()) } } - return &csi.ValidateVolumeCapabilitiesResponse{}, nil } // ControllerExpandVolume allows the CO to expand the size of a volume // volume id is REQUIRED in csi.ControllerExpandVolumeRequest // capacity range is REQUIRED in csi.ControllerExpandVolumeRequest -func (cs *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest, +func (cs *DiskControllerServer) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest, ) (*csi.ControllerExpandVolumeResponse, error) { - defer server.EntryFunction("ControllerExpandVolume")() + defer common.EntryFunction("ControllerExpandVolume")() // 0. check input args // require volume id parameter if len(req.GetVolumeId()) == 0 { return nil, status.Error(codes.InvalidArgument, "No volume id is provided") } - vm, err := volume.NewVolumeManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - // 1. Check volume status // does volume exist volumeId := req.GetVolumeId() - volInfo, err := vm.FindVolume(volumeId) + volInfo, err := cs.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -503,29 +482,29 @@ func (cs *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi return nil, status.Errorf(codes.NotFound, "Volume: %s does not exist", volumeId) } // volume in use - if *volInfo.Status == volume.DiskStatusInuse { + if *volInfo.Status == cloudprovider.DiskStatusInuse { return nil, status.Errorf(codes.FailedPrecondition, "Volume [%s] currently published on a node but plugin only support OFFLINE expansion", volumeId) } // 2. Get capacity volTypeInt := *volInfo.VolumeType - if volTypeStr, ok := server.VolumeTypeToString[volTypeInt]; ok == true { + if volTypeStr, ok := driver.VolumeTypeName[volTypeInt]; ok == true { glog.Infof("Succeed to get volume [%s] type [%s]", volumeId, volTypeStr) } else { glog.Errorf("Unsupported volume [%s] type [%d]", volumeId, volTypeInt) return nil, status.Errorf(codes.Internal, "Unsupported volume [%s] type [%d]", volumeId, volTypeInt) } - volTypeMinSize := server.VolumeTypeToMinSize[volTypeInt] - volTypeMaxSize := server.VolumeTypeToMaxSize[volTypeInt] + volTypeMinSize := driver.VolumeTypeToMinSize[volTypeInt] + volTypeMaxSize := driver.VolumeTypeToMaxSize[volTypeInt] requiredByte := req.GetCapacityRange().GetRequiredBytes() - requiredGib := server.FormatVolumeSize(volTypeInt, server.ByteCeilToGib(requiredByte)) + requiredGib := driver.FormatVolumeSize(volTypeInt, common.ByteCeilToGib(requiredByte)) limitByte := req.GetCapacityRange().GetLimitBytes() if limitByte == 0 { - limitByte = server.Int64Max + limitByte = common.Int64Max } // check volume range - if server.GibToByte(requiredGib) < requiredByte || server.GibToByte(requiredGib) > limitByte || + if common.GibToByte(requiredGib) < requiredByte || common.GibToByte(requiredGib) > limitByte || requiredGib < volTypeMinSize || requiredGib > volTypeMaxSize { glog.Errorf("Request capacity range [%d, %d] bytes, storage class capacity range [%d, %d] GB, format required size: %d gb", requiredByte, limitByte, volTypeMinSize, volTypeMaxSize, requiredGib) @@ -533,21 +512,21 @@ func (cs *controllerServer) ControllerExpandVolume(ctx context.Context, req *csi } // 3. Expand volume - err = vm.ResizeVolume(volumeId, requiredGib) + err = cs.cloud.ResizeVolume(volumeId, requiredGib) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } return &csi.ControllerExpandVolumeResponse{ - CapacityBytes: int64(requiredGib) * server.Gib, + CapacityBytes: int64(requiredGib) * common.Gib, NodeExpansionRequired: true, }, nil } -func (cs *controllerServer) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) { +func (cs *DiskControllerServer) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) { return nil, status.Error(codes.Unimplemented, "") } -func (cs *controllerServer) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { +func (cs *DiskControllerServer) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { return nil, status.Error(codes.Unimplemented, "") } @@ -559,12 +538,14 @@ func (cs *controllerServer) GetCapacity(ctx context.Context, req *csi.GetCapacit // the plugin SHOULD return 0 OK and ready_to_use SHOULD be set to false. // Source volume id is REQUIRED // Snapshot name is REQUIRED -func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { +func (cs *DiskControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, + error) { glog.Info("----- Start CreateSnapshot -----") defer glog.Info("===== End CreateSnapshot =====") - if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); err != nil { + if isValid := cs.driver.ValidateControllerServiceRequest(csi. + ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); isValid != true { glog.Errorf("invalid create snapshot request: %v", req) - return nil, err + return nil, status.Error(codes.PermissionDenied, "") } // 0. Preflight // Check source volume id @@ -578,11 +559,6 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS } // Create snapshot manager object - glog.Infof("Create snapshot manager object from file [%s]", cs.cloudServer.GetConfigFilePath()) - sm, err := snapshot.NewSnapshotManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } srcVolId := req.GetSourceVolumeId() snapName := req.GetName() var ts *timestamp.Timestamp @@ -592,7 +568,7 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS // be specified as a volume_content_source in a CreateVolumeRequest), the Plugin MUST reply 0 OK with the // corresponding CreateSnapshotResponse. glog.Infof("Find existing snapshot name [%s]", snapName) - exSnap, err := sm.FindSnapshotByName(snapName) + exSnap, err := cs.cloud.FindSnapshotByName(snapName) if err != nil { return nil, status.Errorf(codes.Internal, "find snapshot by name error: %s, %s", snapName, err.Error()) } @@ -605,14 +581,14 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - if *exSnap.Status == snapshot.SnapshotStatusAvailable { + if *exSnap.Status == cloudprovider.SnapshotStatusAvailable { isReadyToUse = true } else { isReadyToUse = false } return &csi.CreateSnapshotResponse{ Snapshot: &csi.Snapshot{ - SizeBytes: int64(*exSnap.Size) * server.Mib, + SizeBytes: int64(*exSnap.Size) * common.Mib, SnapshotId: *exSnap.SnapshotID, SourceVolumeId: *exSnap.Resource.ResourceID, CreationTime: ts, @@ -625,15 +601,15 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS snapName, *exSnap.SnapshotID, srcVolId) } // Create a new full snapshot - glog.Infof("Creating snapshot [%s] from volume [%s] in zone [%s]...", snapName, srcVolId, sm.GetZone()) - snapId, err := sm.CreateSnapshot(snapName, srcVolId) + glog.Infof("Creating snapshot [%s] from volume [%s] in zone [%s]...", snapName, srcVolId, cs.cloud.GetZone()) + snapId, err := cs.cloud.CreateSnapshot(snapName, srcVolId) if err != nil { return nil, status.Errorf(codes.Internal, "create snapshot [%s] from source volume [%s] error: %s", snapName, srcVolId, err.Error()) } glog.Infof("Create snapshot [%s] finished, get snapshot id [%s]", snapName, snapId) glog.Infof("Get snapshot id [%s] info...", snapId) - snapInfo, err := sm.FindSnapshot(snapId) + snapInfo, err := cs.cloud.FindSnapshot(snapId) if err != nil { return nil, status.Errorf(codes.Internal, "Find snapshot [%s] error: %s", snapId, err.Error()) } @@ -645,14 +621,14 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - if *snapInfo.Status == snapshot.SnapshotStatusAvailable { + if *snapInfo.Status == cloudprovider.SnapshotStatusAvailable { isReadyToUse = true } else { isReadyToUse = false } return &csi.CreateSnapshotResponse{ Snapshot: &csi.Snapshot{ - SizeBytes: int64(*snapInfo.Size) * server.Mib, + SizeBytes: int64(*snapInfo.Size) * common.Mib, SnapshotId: *snapInfo.SnapshotID, SourceVolumeId: *snapInfo.Resource.ResourceID, CreationTime: ts, @@ -664,12 +640,14 @@ func (cs *controllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS // CreateSnapshot allows the CO to delete a snapshot. // This operation MUST be idempotent. // Snapshot id is REQUIRED -func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) { +func (cs *DiskControllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, + error) { glog.Info("----- Start DeleteSnapshot -----") defer glog.Info("===== End DeleteSnapshot =====") - if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); err != nil { + if isValid := cs.driver.ValidateControllerServiceRequest(csi. + ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); isValid != true { glog.Errorf("invalid create snapshot request: %v", req) - return nil, err + return nil, status.Error(codes.PermissionDenied, "") } // 0. Preflight // Check snapshot id @@ -678,18 +656,10 @@ func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteS return nil, status.Error(codes.InvalidArgument, "snapshot ID missing in request") } snapId := req.GetSnapshotId() - - // Create snapshot manager object - glog.Infof("Create snapshot manager object from file [%s]", cs.cloudServer.GetConfigFilePath()) - sm, err := snapshot.NewSnapshotManagerFromFile(cs.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - // 1. For idempotent: // MUST reply OK when snapshot does not exist glog.Infof("Find existing snapshot id [%s].", snapId) - exSnap, err := sm.FindSnapshot(snapId) + exSnap, err := cs.cloud.FindSnapshot(snapId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -698,16 +668,16 @@ func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteS return &csi.DeleteSnapshotResponse{}, nil } // 2. Delete snapshot - glog.Infof("Deleting snapshot id [%s] in zone [%s]...", snapId, sm.GetZone()) + glog.Infof("Deleting snapshot id [%s] in zone [%s]...", snapId, cs.cloud.GetZone()) // When return with retry message at deleting snapshot, retry after several seconds. // Retry times is 10. // Retry interval is changed from 1 second to 10 seconds. for i := 1; i <= 10; i++ { glog.Infof("Try to delete snapshot id [%s] in [%d] time(s)", snapId, i) - err = sm.DeleteSnapshot(snapId) + err = cs.cloud.DeleteSnapshot(snapId) if err != nil { - glog.Infof("Failed to delete snapshot id [%s] in zone [%s] with error: %v", snapId, sm.GetZone(), err) - if strings.Contains(err.Error(), server.RetryString) { + glog.Infof("Failed to delete snapshot id [%s] in zone [%s] with error: %v", snapId, cs.cloud.GetZone(), err) + if strings.Contains(err.Error(), cloudprovider.RetryString) { sleepTime := time.Duration(i*2) * time.Second glog.Infof("Retry to delete snapshot id [%s] after [%f] second(s).", snapId, sleepTime.Seconds()) time.Sleep(sleepTime) @@ -722,6 +692,13 @@ func (cs *controllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteS return nil, status.Error(codes.Internal, "Exceed retry times: "+err.Error()) } -func (cs *controllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { +func (cs *DiskControllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { return nil, status.Error(codes.Unimplemented, "") } + +func (cs *DiskControllerServer) ControllerGetCapabilities(ctx context.Context, + req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { + return &csi.ControllerGetCapabilitiesResponse{ + Capabilities: cs.driver.GetControllerCapability(), + }, nil +} diff --git a/pkg/disk/identityserver.go b/pkg/disk/rpcserver/identityserver.go similarity index 52% rename from pkg/disk/identityserver.go rename to pkg/disk/rpcserver/identityserver.go index 1f731041..842fd54b 100644 --- a/pkg/disk/identityserver.go +++ b/pkg/disk/rpcserver/identityserver.go @@ -14,31 +14,35 @@ // | limitations under the License. // +------------------------------------------------------------------------- -package disk +package rpcserver import ( "github.com/container-storage-interface/spec/lib/go/csi" "github.com/golang/glog" - "github.com/kubernetes-csi/drivers/pkg/csi-common" - "github.com/yunify/qingcloud-csi/pkg/server" - "github.com/yunify/qingcloud-csi/pkg/server/zone" + "github.com/yunify/qingcloud-csi/pkg/cloudprovider" + "github.com/yunify/qingcloud-csi/pkg/disk/driver" "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -type identityServer struct { - *csicommon.DefaultIdentityServer - cloudServer *server.ServerConfig +type DiskIdentityServer struct { + driver *driver.DiskDriver + cloud cloudprovider.CloudManager } -// Plugin MUST implement this RPC call -func (is *identityServer) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) { - zm, err := zone.NewZoneManagerFromFile(is.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) +// NewIdentityServer +// Create identity server +func NewIdentityServer(d *driver.DiskDriver, c cloudprovider.CloudManager) *DiskIdentityServer { + return &DiskIdentityServer{ + driver: d, + cloud: c, } - zones, err := zm.GetZoneList() +} + +// Plugin MUST implement this RPC call +func (is *DiskIdentityServer) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) { + zones, err := is.cloud.GetZoneList() if err != nil { return nil, status.Error(codes.FailedPrecondition, err.Error()) } @@ -47,24 +51,28 @@ func (is *identityServer) Probe(ctx context.Context, req *csi.ProbeRequest) (*cs } // Get plugin capabilities: CONTROLLER, ACCESSIBILITY, EXPANSION -func (ids *identityServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) { +func (d *DiskIdentityServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi. + GetPluginCapabilitiesResponse, error) { glog.V(5).Infof("Using default capabilities") return &csi.GetPluginCapabilitiesResponse{ - Capabilities: []*csi.PluginCapability{ - { - Type: &csi.PluginCapability_Service_{ - Service: &csi.PluginCapability_Service{ - Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, - }, - }, - }, - { - Type: &csi.PluginCapability_VolumeExpansion_{ - VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ - Type: csi.PluginCapability_VolumeExpansion_OFFLINE, - }, - }, - }, - }, + Capabilities: d.driver.GetPluginCapability(), + }, nil +} + +func (d *DiskIdentityServer) GetPluginInfo(ctx context.Context, + req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { + glog.V(5).Infof("Using GetPluginInfo") + + if d.driver.GetName() == "" { + return nil, status.Error(codes.Unavailable, "Driver name not configured") + } + + if d.driver.GetVersion() == "" { + return nil, status.Error(codes.Unavailable, "Driver is missing version") + } + + return &csi.GetPluginInfoResponse{ + Name: d.driver.GetName(), + VendorVersion: d.driver.GetVersion(), }, nil } diff --git a/pkg/disk/nodeserver.go b/pkg/disk/rpcserver/nodeserver.go similarity index 75% rename from pkg/disk/nodeserver.go rename to pkg/disk/rpcserver/nodeserver.go index 4758c963..35f3e606 100644 --- a/pkg/disk/nodeserver.go +++ b/pkg/disk/rpcserver/nodeserver.go @@ -14,16 +14,15 @@ // | limitations under the License. // +------------------------------------------------------------------------- -package disk +package rpcserver import ( "fmt" "github.com/container-storage-interface/spec/lib/go/csi" "github.com/golang/glog" - "github.com/kubernetes-csi/drivers/pkg/csi-common" - "github.com/yunify/qingcloud-csi/pkg/server" - "github.com/yunify/qingcloud-csi/pkg/server/storageclass" - "github.com/yunify/qingcloud-csi/pkg/server/volume" + "github.com/yunify/qingcloud-csi/pkg/cloudprovider" + "github.com/yunify/qingcloud-csi/pkg/common" + "github.com/yunify/qingcloud-csi/pkg/disk/driver" "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -32,9 +31,18 @@ import ( "os" ) -type nodeServer struct { - *csicommon.DefaultNodeServer - cloudServer *server.ServerConfig +type DiskNodeServer struct { + driver *driver.DiskDriver + cloud cloudprovider.CloudManager +} + +// NewNodeServer +// Create node server +func NewNodeServer(d *driver.DiskDriver, c cloudprovider.CloudManager) *DiskNodeServer { + return &DiskNodeServer{ + driver: d, + cloud: c, + } } // This operation MUST be idempotent @@ -44,7 +52,8 @@ type nodeServer struct { // target path + Required // volume capability + Required // read only + Required (This field is NOT provided when requesting in Kubernetes) -func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { +func (ns *DiskNodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi. + NodePublishVolumeResponse, error) { glog.Info("----- Start NodePublishVolume -----") defer glog.Info("===== End NodePublishVolume =====") // 0. Preflight @@ -59,7 +68,7 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis // Check volume capability if req.GetVolumeCapability() == nil { return nil, status.Error(codes.InvalidArgument, "Volume capabilities missing in request") - } else if !server.ContainsVolumeCapability(ns.Driver.GetVolumeCapabilityAccessModes(), req.GetVolumeCapability()) { + } else if !ns.driver.ValidateVolumeCapability(req.GetVolumeCapability()) { return nil, status.Error(codes.FailedPrecondition, "Exceed capabilities") } // check stage path @@ -72,19 +81,14 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis volumeId := req.GetVolumeId() // set fsType - qc, err := storageclass.NewQingStorageClassFromMap(req.GetVolumeContext()) + qc, err := driver.NewQingStorageClassFromMap(req.GetVolumeContext()) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - fsType := qc.VolumeFsType + fsType := qc.FsType - // Create VolumeManager object - vm, err := volume.NewVolumeManagerFromFile(ns.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } // Check volume exist - volInfo, err := vm.FindVolume(volumeId) + volInfo, err := ns.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -136,7 +140,8 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis // csi.NodeUnpublishVolumeRequest: volume id + Required // target path + Required -func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { +func (ns *DiskNodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi. + NodeUnpublishVolumeResponse, error) { glog.Info("----- Start NodeUnpublishVolume -----") defer glog.Info("===== End NodeUnpublishVolume =====") // 0. Preflight @@ -151,13 +156,8 @@ func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu volumeId := req.GetVolumeId() targetPath := req.GetTargetPath() - // Create VolumeManager object - vm, err := volume.NewVolumeManagerFromFile(ns.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } // Check volume exist - volInfo, err := vm.FindVolume(volumeId) + volInfo, err := ns.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -190,13 +190,11 @@ func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu // csi.NodeStageVolumeRequest: volume id + Required // stage target path + Required // volume capability + Required -func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { +func (ns *DiskNodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, + error) { glog.Info("----- Start NodeStageVolume -----") defer glog.Info("===== End NodeStageVolume =====") - capRsp, _ := ns.NodeGetCapabilities(context.Background(), nil) - if flag := server.ContainsNodeServiceCapability(capRsp.GetCapabilities(), - csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME); flag == false { - glog.Errorf("driver capability %v", capRsp.GetCapabilities()) + if flag := ns.driver.ValidateNodeServiceRequest(csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME); flag == false { return nil, status.Error(codes.Unimplemented, "Node has not stage capability") } // 0. Preflight @@ -214,19 +212,14 @@ func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol volumeId := req.GetVolumeId() targetPath := req.GetStagingTargetPath() // set fsType - qc, err := storageclass.NewQingStorageClassFromMap(req.GetPublishContext()) + qc, err := driver.NewQingStorageClassFromMap(req.GetPublishContext()) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - fsType := qc.VolumeFsType + fsType := qc.FsType - // Create VolumeManager object - vm, err := volume.NewVolumeManagerFromFile(ns.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } // Check volume exist - volInfo, err := vm.FindVolume(volumeId) + volInfo, err := ns.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -272,13 +265,11 @@ func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol // This operation MUST be idempotent // csi.NodeUnstageVolumeRequest: volume id + Required // target path + Required -func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { +func (ns *DiskNodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi. + NodeUnstageVolumeResponse, error) { glog.Info("----- Start NodeUnstageVolume -----") defer glog.Info("===== End NodeUnstageVolume =====") - capRsp, _ := ns.NodeGetCapabilities(context.Background(), nil) - if flag := server.ContainsNodeServiceCapability(capRsp.GetCapabilities(), - csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME); flag == false { - glog.Errorf("driver capability %v", capRsp.GetCapabilities()) + if flag := ns.driver.ValidateNodeServiceRequest(csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME); flag == false { return nil, status.Error(codes.Unimplemented, "Node has not unstage capability") } // 0. Preflight @@ -293,14 +284,8 @@ func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag volumeId := req.GetVolumeId() targetPath := req.GetStagingTargetPath() - // Create VolumeManager object - vm, err := volume.NewVolumeManagerFromFile(ns.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - // Check volume exist - volInfo, err := vm.FindVolume(volumeId) + volInfo, err := ns.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -335,49 +320,35 @@ func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag cnt-- glog.Infof("disk volume mount count: %d", cnt) if cnt > 0 { - glog.Errorf("image %s still mounted in instance %s", volumeId, ns.cloudServer.GetInstanceId()) + glog.Errorf("image %s still mounted in instance %s", volumeId, ns.driver.GetInstanceId()) return nil, status.Error(codes.Internal, "unmount failed") } return &csi.NodeUnstageVolumeResponse{}, nil } -func (ns *nodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { +func (ns *DiskNodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi. + NodeGetCapabilitiesResponse, error) { glog.Info("----- Start NodeGetCapabilities -----") defer glog.Info("===== End NodeGetCapabilities =====") return &csi.NodeGetCapabilitiesResponse{ - Capabilities: []*csi.NodeServiceCapability{ - { - Type: &csi.NodeServiceCapability_Rpc{ - Rpc: &csi.NodeServiceCapability_RPC{ - Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, - }, - }, - }, - { - Type: &csi.NodeServiceCapability_Rpc{ - Rpc: &csi.NodeServiceCapability_RPC{ - Type: csi.NodeServiceCapability_RPC_EXPAND_VOLUME, - }, - }, - }, - }, + Capabilities: ns.driver.GetNodeCapability(), }, nil } -func (ns *nodeServer) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { +func (ns *DiskNodeServer) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { glog.V(2).Info("----- Start NodeGetInfo -----") defer glog.Info("===== End NodeGetInfo =====") return &csi.NodeGetInfoResponse{ - NodeId: ns.cloudServer.GetInstanceId(), - MaxVolumesPerNode: ns.cloudServer.GetMaxVolumePerNode(), + NodeId: ns.driver.GetInstanceId(), + MaxVolumesPerNode: ns.driver.GetMaxVolumePerNode(), }, nil } -func (ns *nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) ( +func (ns *DiskNodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) ( *csi.NodeExpandVolumeResponse, error) { - defer server.EntryFunction("NodeExpandVolume")() + defer common.EntryFunction("NodeExpandVolume")() // 0. Preflight // check arguments if len(req.GetVolumeId()) == 0 { @@ -390,14 +361,8 @@ func (ns *nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV volumeId := req.GetVolumeId() volumePath := req.GetVolumePath() - // Create VolumeManager object - vm, err := volume.NewVolumeManagerFromFile(ns.cloudServer.GetConfigFilePath()) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - // Check volume exist - volInfo, err := vm.FindVolume(volumeId) + volInfo, err := ns.cloud.FindVolume(volumeId) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } @@ -423,3 +388,8 @@ func (ns *nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV } return &csi.NodeExpandVolumeResponse{}, nil } + +func (ns *DiskNodeServer) NodeGetVolumeStats(ctx context.Context, + req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) { + return nil, status.Error(codes.Unimplemented, "") +} diff --git a/pkg/disk/rpcserver/server.go b/pkg/disk/rpcserver/server.go new file mode 100644 index 00000000..a0a6ab35 --- /dev/null +++ b/pkg/disk/rpcserver/server.go @@ -0,0 +1,20 @@ +package rpcserver + +import ( + "github.com/yunify/qingcloud-csi/pkg/cloudprovider" + "github.com/yunify/qingcloud-csi/pkg/common" + "github.com/yunify/qingcloud-csi/pkg/disk/driver" +) + +// Run +// Initial and start CSI driver +func Run(driver *driver.DiskDriver, cloud cloudprovider.CloudManager, endpoint string) { + // Initialize default library driver + ids := NewIdentityServer(driver, cloud) + cs := NewControllerServer(driver, cloud) + ns := NewNodeServer(driver, cloud) + + s := common.NewNonBlockingGRPCServer() + s.Start(endpoint, ids, cs, ns) + s.Wait() +} diff --git a/pkg/server/instance/instance_manager.go b/pkg/server/instance/instance_manager.go deleted file mode 100644 index ba72364f..00000000 --- a/pkg/server/instance/instance_manager.go +++ /dev/null @@ -1,108 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 instance - -import ( - "fmt" - "github.com/golang/glog" - "github.com/yunify/qingcloud-csi/pkg/server" - qcconfig "github.com/yunify/qingcloud-sdk-go/config" - qcservice "github.com/yunify/qingcloud-sdk-go/service" -) - -const ( - InstanceStatusPending string = "pending" - InstanceStatusRunning string = "running" - InstanceStatusStopped string = "stopped" - InstanceStatusSuspended string = "suspended" - InstanceStatusTerminated string = "terminated" - InstanceStatusCreased string = "ceased" -) - -type InstanceManager interface { - FindInstance(id string) (instance *qcservice.Instance, err error) -} - -type instanceManager struct { - instanceService *qcservice.InstanceService - jobService *qcservice.JobService -} - -// NewInstanceManagerFromConfig: Create instance manager from config -func NewInstanceManagerFromConfig(config *qcconfig.Config) (InstanceManager, error) { - // initial qingcloud iaas service - qs, err := qcservice.Init(config) - if err != nil { - return nil, err - } - // create volume service - is, _ := qs.Instance(config.Zone) - // create job service - js, _ := qs.Job(config.Zone) - // initial volume provisioner - im := instanceManager{ - instanceService: is, - jobService: js, - } - glog.Infof("Finish initial instance manager") - return &im, nil -} - -// NewInstanceManagerFromFile -// Create instance manager from file -func NewInstanceManagerFromFile(filePath string) (InstanceManager, error) { - // create config - config, err := server.ReadConfigFromFile(filePath) - if err != nil { - return nil, err - } - return NewInstanceManagerFromConfig(config) -} - -// Find instance by instance ID -// Return: nil, nil: not found instance -// instance, nil: found instance -// nil, error: internal error -func (iv *instanceManager) FindInstance(id string) (instance *qcservice.Instance, err error) { - // set describe instance input - input := qcservice.DescribeInstancesInput{} - var seeCluster int = 1 - input.IsClusterNode = &seeCluster - input.Instances = append(input.Instances, &id) - // call describe instance - output, err := iv.instanceService.DescribeInstances(&input) - // error - if err != nil { - return nil, err - } - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return nil, fmt.Errorf(*output.Message) - } - // not found instances - switch *output.TotalCount { - case 0: - return nil, nil - case 1: - if *output.InstanceSet[0].Status == InstanceStatusCreased || *output.InstanceSet[0].Status == InstanceStatusTerminated { - return nil, nil - } - return output.InstanceSet[0], nil - default: - return nil, fmt.Errorf("Find duplicate instances id %s in %s", id, iv.instanceService.Config.Zone) - } -} diff --git a/pkg/server/snapshot/snapshot_manager.go b/pkg/server/snapshot/snapshot_manager.go deleted file mode 100644 index 9cf70705..00000000 --- a/pkg/server/snapshot/snapshot_manager.go +++ /dev/null @@ -1,255 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 snapshot - -import ( - "fmt" - "github.com/golang/glog" - "github.com/yunify/qingcloud-csi/pkg/server" - qcclient "github.com/yunify/qingcloud-sdk-go/client" - qcconfig "github.com/yunify/qingcloud-sdk-go/config" - qcservice "github.com/yunify/qingcloud-sdk-go/service" -) - -// https://github.com/yunify/qingcloud-sdk-go/blob/c8f8d40dd4793219c129b7516d6f8ae130bc83c9/service/types.go#L2763 -// Available status values: pending, available, suspended, deleted, ceased -const ( - SnapshotStatusPending string = "pending" - SnapshotStatusAvailable string = "available" - SnapshotStatusSuspended string = "suspended" - SnapshotStatusDeleted string = "deleted" - SnapshotStatusCeased string = "ceased" -) - -// https://github.com/yunify/qingcloud-sdk-go/blob/c8f8d40dd4793219c129b7516d6f8ae130bc83c9/service/types.go#L2770 -// Available transition status values: creating, suspending, resuming, deleting, recovering -const ( - SnapshotTransitionStatusCreating string = "creating" - SnapshotTransitionStatusSuspending string = "suspending" - SnapshotTransitionStatusResuming string = "resuming" - SnapshotTransitionStatusDeleting string = "deleting" - SnapshotTransitionStatusRecovering string = "recovering" -) - -const ( - SnapshotFull int = 1 - SnapshotIncrement int = 0 -) - -type SnapshotManager interface { - FindSnapshot(id string) (snapshot *qcservice.Snapshot, err error) - FindSnapshotByName(name string) (snapshot *qcservice.Snapshot, err error) - CreateSnapshot(snapshotName string, resourceId string) (snapshotId string, err error) - DeleteSnapshot(snapshotId string) error - GetZone() string - waitJob(jobId string) error -} - -type snapshotManager struct { - snapshotService *qcservice.SnapshotService - jobService *qcservice.JobService -} - -// NewSnapshotManagerFromConfig -// Create snapshot manager from config -func NewSnapshotManagerFromConfig(config *qcconfig.Config) (SnapshotManager, error) { - // initial qingcloud iaas service - qs, err := qcservice.Init(config) - if err != nil { - return nil, err - } - // create snapshot service - ss, _ := qs.Snapshot(config.Zone) - // create job service - js, _ := qs.Job(config.Zone) - // initial snapshot manager - sm := snapshotManager{ - snapshotService: ss, - jobService: js, - } - glog.Infof("Finished initial snapshot manager") - return &sm, nil -} - -// NewSnapshotManagerFromFile -// Create snapshot manager from file -func NewSnapshotManagerFromFile(filePath string) (SnapshotManager, error) { - config, err := server.ReadConfigFromFile(filePath) - if err != nil { - glog.Errorf("Failed read config file [%s], error: [%s]", filePath, err.Error()) - return nil, err - } - glog.Infof("Succeed read config file [%s]", filePath) - return NewSnapshotManagerFromConfig(config) -} - -// Find snapshot by snapshot id -// Return: nil, nil: not found snapshot -// snapshot, nil: found snapshot -// nil, error: internal error -func (sm *snapshotManager) FindSnapshot(id string) (snapshot *qcservice.Snapshot, err error) { - // Set DescribeSnapshot input - input := qcservice.DescribeSnapshotsInput{} - input.Snapshots = append(input.Snapshots, &id) - // Call describe snapshot - output, err := sm.snapshotService.DescribeSnapshots(&input) - // 1. Error is not equal to nil. - if err != nil { - return nil, err - } - // 2. Return code is not equal to 0. - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return nil, fmt.Errorf("Call IaaS DescribeSnapshot err: snapshot id %s in %s", - id, sm.snapshotService.Config.Zone) - } - switch *output.TotalCount { - // Not found snapshot - case 0: - return nil, nil - // Found one snapshot - case 1: - if *output.SnapshotSet[0].Status == SnapshotStatusCeased || - *output.SnapshotSet[0].Status == SnapshotStatusDeleted { - return nil, nil - } - return output.SnapshotSet[0], nil - // Found duplicate snapshots - default: - return nil, - fmt.Errorf("Call IaaS DescribeSnapshot err: find duplicate snapshot, snapshot id %s in %s", - id, sm.snapshotService.Config.Zone) - } -} - -// Find snapshot by snapshot name -// In Qingcloud IaaS platform, it is possible that two snapshots have the same name. -// In Kubernetes, the CO will set a unique PV name. -// CSI driver take the PV name as a snapshot name. -// Return: nil, nil: not found snapshots -// snapshots, nil: found snapshot -// nil, error: internal error -func (sm *snapshotManager) FindSnapshotByName(name string) (snapshot *qcservice.Snapshot, err error) { - if len(name) == 0 { - return nil, nil - } - // Set input arguments - input := qcservice.DescribeSnapshotsInput{} - input.SearchWord = &name - // Call DescribeSnapshot - output, err := sm.snapshotService.DescribeSnapshots(&input) - // Handle error - if err != nil { - return nil, err - } - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return nil, fmt.Errorf("Call IaaS DescribeSnapshots err: snapshot name %s in %s", - name, sm.snapshotService.Config.Zone) - } - // Not found snapshots - for _, v := range output.SnapshotSet { - if *v.SnapshotName != name { - continue - } - if *v.Status == SnapshotStatusCeased || *v.Status == SnapshotStatusDeleted { - continue - } - return v, nil - } - return nil, nil -} - -// CreateSnapshot -// 1. format snapshot size -// 2. create snapshot -// 3. wait job -func (sm *snapshotManager) CreateSnapshot(snapshotName string, resourceId string) (snapshotId string, err error) { - // 0. Set CreateSnapshot args - // set input value - input := &qcservice.CreateSnapshotsInput{} - // snapshot name - input.SnapshotName = &snapshotName - // full snapshot - snapshotType := int(SnapshotFull) - input.IsFull = &snapshotType - // resource volume id - input.Resources = []*string{&resourceId} - - // 1. Create snapshot - glog.Infof("Call IaaS CreateSnapshot request snapshot name: %s, zone: %s, resource id %s, is full snapshot %T", - *input.SnapshotName, sm.GetZone(), *input.Resources[0], *input.IsFull == SnapshotFull) - output, err := sm.snapshotService.CreateSnapshots(input) - if err != nil { - return "", err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return "", fmt.Errorf(*output.Message) - } - snapshotId = *output.Snapshots[0] - glog.Infof("Call IaaS CreateSnapshots snapshot name %s snapshot id %s succeed", snapshotName, snapshotId) - return snapshotId, nil -} - -// DeleteSnapshot -// 1. delete snapshot by id -// 2. wait job -func (sm *snapshotManager) DeleteSnapshot(snapshotId string) error { - // set input value - input := &qcservice.DeleteSnapshotsInput{} - input.Snapshots = append(input.Snapshots, &snapshotId) - // delete snapshot - glog.Infof("Call IaaS DeleteSnapshot request id: %s, zone: %s", - snapshotId, *sm.snapshotService.Properties.Zone) - output, err := sm.snapshotService.DeleteSnapshots(input) - if err != nil { - return err - } - // wait job - glog.Infof("Call IaaS WaitJob %s", *output.JobID) - if err := sm.waitJob(*output.JobID); err != nil { - return err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return fmt.Errorf(*output.Message) - } - glog.Infof("Call IaaS DeleteSnapshot %s succeed", snapshotId) - return nil -} - -// GetZone -// Get current zone in Qingcloud IaaS -func (sm *snapshotManager) GetZone() string { - if sm == nil || sm.snapshotService == nil || sm.snapshotService.Properties == nil || sm.snapshotService.Properties. - Zone == nil { - return "" - } - return *sm.snapshotService.Properties.Zone -} - -func (vm *snapshotManager) waitJob(jobId string) error { - err := qcclient.WaitJob(vm.jobService, jobId, server.OperationWaitTimeout, server.WaitInterval) - if err != nil { - glog.Error("Call Iaas WaitJob: ", jobId) - return err - } - return nil -} diff --git a/pkg/server/snapshot/snapshot_manager_test.go b/pkg/server/snapshot/snapshot_manager_test.go deleted file mode 100644 index d7d79719..00000000 --- a/pkg/server/snapshot/snapshot_manager_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package snapshot - -import ( - "testing" -) - -var ( - // Tester should set these variables before executing unit test. - volumeId1 string = "vol-uwxrtw0d" - volumeName1 string = "test2" - snapshotId1 string = "ss-rnbwvjy5" - snapshotName1 string = "test1" -) - -var getsm = func() SnapshotManager { - // get storage class - filePath := "/root/.qingcloud/config.yaml" - sm, err := NewSnapshotManagerFromFile(filePath) - if err != nil { - return nil - } - return sm -} - -func TestSnapshotManager_FindSnapshot(t *testing.T) { - sm := getsm() - // testcase - testcases := []struct { - name string - id string - result bool - }{ - { - name: "Available", - id: snapshotId1, - result: true, - }, - { - name: "Not found", - id: snapshotId1 + "fake", - result: false, - }, - { - name: "By name", - id: snapshotName1, - result: false, - }, - } - - // test findVolume - for _, v := range testcases { - snap, err := sm.FindSnapshot(v.id) - if err != nil { - t.Error("find volume error: ", err.Error()) - } - res := snap != nil - if res != v.result { - t.Errorf("name: %s, expect %t, actually %t", v.name, v.result, res) - } - } -} - -func TestSnapshotManager_FindSnapshotByName(t *testing.T) { - sm := getsm() - // testcase - testcases := []struct { - name string - snapshot string - result bool - }{ - { - name: "Available", - snapshot: snapshotName1, - result: true, - }, - { - name: "Ceased", - snapshot: "sanity", - result: false, - }, - { - name: "Volume id", - snapshot: snapshotId1, - result: false, - }, - { - name: "Substring", - snapshot: string((snapshotName1)[:2]), - result: false, - }, - { - name: "Null string", - snapshot: "", - result: false, - }, - } - - // test findVolume - for _, v := range testcases { - snap, err := sm.FindSnapshotByName(v.snapshot) - if err != nil { - t.Error("find volume error: ", err.Error()) - } - res := snap != nil - if res != v.result { - t.Errorf("name %s, expect %t, actually %t", v.name, v.result, res) - } - } -} - -func TestSnapshotManager_CreateSnapshot(t *testing.T) { - sm := getsm() - - testcases := []struct { - name string - snapName string - sourceVolId string - result bool - snapId string - }{ - { - name: "create snapshot name unittest-1", - snapName: "unittest-1", - sourceVolId: volumeId1, - result: true, - snapId: "", - }, - { - name: "create snapshot name unittest-1 repeatedly", - snapName: "unittest-1", - sourceVolId: volumeId1, - result: true, - snapId: "", - }, - { - name: "create volume name unittest-2", - snapName: "unittest-2", - sourceVolId: volumeId1, - result: true, - snapId: "", - }, - } - for i, v := range testcases { - snapId, err := sm.CreateSnapshot(v.snapName, v.sourceVolId) - if err != nil { - t.Errorf("test %s: %s", v.name, err.Error()) - } else { - snap, _ := sm.FindSnapshot(snapId) - testcases[i].snapId = *snap.SnapshotID - if *snap.SnapshotName != v.snapName { - t.Errorf("test %s: expect %s but actually %s", v.name, v.snapName, *snap.SnapshotName) - } - } - } -} - -func TestDeleteVolume(t *testing.T) { - sm := getsm() - // testcase - testcases := []struct { - name string - id string - isError bool - }{ - { - name: "delete first volume", - id: snapshotId1, - isError: false, - }, - { - name: "delete first volume repeatedly", - id: snapshotId1, - isError: true, - }, - { - name: "delete not exist volume", - id: "ss-1234567", - isError: true, - }, - } - for _, v := range testcases { - err := sm.DeleteSnapshot(v.id) - if err != nil && !v.isError { - t.Errorf("error name %s: %s", v.name, err.Error()) - } - } -} diff --git a/pkg/server/storageclass/storage_class.go b/pkg/server/storageclass/storage_class.go deleted file mode 100644 index 4dbf017d..00000000 --- a/pkg/server/storageclass/storage_class.go +++ /dev/null @@ -1,141 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 storageclass - -import ( - "fmt" - "github.com/yunify/qingcloud-csi/pkg/server" - "strconv" -) - -type QingStorageClass struct { - VolumeType int `json:"type"` - VolumeMaxSize int `json:"maxSize"` - VolumeMinSize int `json:"minSize"` - VolumeStepSize int `json:"stepSize"` - VolumeFsType string `json:"fsType"` - VolumeReplica int `json:"replica"` -} - -// NewDefaultQingStorageClass create default qingStorageClass object -func NewDefaultQingStorageClass() *QingStorageClass { - return NewDefaultQingStorageClassFromType(server.SSDEnterpriseDiskType) -} - -// NewDefaultQingStorageClassFromType create default qingStorageClass by specified volume type -func NewDefaultQingStorageClassFromType(volumeType int) *QingStorageClass { - if server.IsValidVolumeType(volumeType) != true { - return nil - } - return &QingStorageClass{ - VolumeType: volumeType, - VolumeMaxSize: server.VolumeTypeToMaxSize[volumeType], - VolumeMinSize: server.VolumeTypeToMinSize[volumeType], - VolumeStepSize: server.VolumeTypeToStepSize[volumeType], - VolumeFsType: server.FileSystemDefault, - VolumeReplica: server.DefaultReplica, - } -} - -// NewQingStorageClassFromMap create qingStorageClass object from map -func NewQingStorageClassFromMap(opt map[string]string) (*QingStorageClass, error) { - sVolType, volTypeOk := opt["type"] - sMaxSize, maxSizeOk := opt["maxSize"] - sMinSize, minSizeOk := opt["minSize"] - sStepSize, stepSizeOk := opt["stepSize"] - sFsType, fsTypeOk := opt["fsType"] - sReplica, replicaOk := opt["replica"] - if volTypeOk == false { - return NewDefaultQingStorageClass(), nil - } - // Convert volume type to integer - iVolType, err := strconv.Atoi(sVolType) - if err != nil { - return nil, err - } - sc := NewDefaultQingStorageClassFromType(iVolType) - if maxSizeOk == true && minSizeOk == true && stepSizeOk == true { - // Get volume maxsize - iMaxSize, err := strconv.Atoi(sMaxSize) - if err != nil { - return nil, err - } - if iMaxSize < 0 { - return nil, fmt.Errorf("MaxSize must not less than zero") - } - sc.VolumeMaxSize = iMaxSize - // Get volume minsize - iMinSize, err := strconv.Atoi(sMinSize) - if err != nil { - return nil, err - } - if iMinSize < 0 { - return nil, fmt.Errorf("MinSize must not less than zero") - } - sc.VolumeMinSize = iMinSize - // Ensure volume minSize less than volume maxSize - if sc.VolumeMaxSize < sc.VolumeMinSize { - return nil, fmt.Errorf("volume maxSize must greater than or equal to volume minSize") - } - // Get volume step size - iStepSize, err := strconv.Atoi(sStepSize) - if err != nil { - return nil, err - } - if iStepSize <= 0 { - return nil, fmt.Errorf("StepSize must greate than zero") - } - sc.VolumeStepSize = iStepSize - } - - if fsTypeOk == true { - if !server.IsValidFileSystemType(sFsType) { - return nil, fmt.Errorf("unsupported fsType %s", sFsType) - } - sc.VolumeFsType = sFsType - } - - // Get volume replicas - if replicaOk == true { - iReplica, err := strconv.Atoi(sReplica) - if err != nil { - return nil, err - } - if !server.IsValidReplica(iReplica) { - return nil, fmt.Errorf("unsupported replicas \"%s\"", sReplica) - } - sc.VolumeReplica = iReplica - } - - return sc, nil -} - -// FormatVolumeSize transfer to proper volume size -func (sc QingStorageClass) FormatVolumeSize(size int, step int) int { - if size <= sc.VolumeMinSize { - return sc.VolumeMinSize - } else if size >= sc.VolumeMaxSize { - return sc.VolumeMaxSize - } - if size%step != 0 { - size = (size/step + 1) * step - } - if size >= sc.VolumeMaxSize { - return sc.VolumeMaxSize - } - return size -} diff --git a/pkg/server/storageclass/storage_class_test.go b/pkg/server/storageclass/storage_class_test.go deleted file mode 100644 index 2c5d9ae3..00000000 --- a/pkg/server/storageclass/storage_class_test.go +++ /dev/null @@ -1,355 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 storageclass - -import ( - "github.com/yunify/qingcloud-csi/pkg/server" - "reflect" - "strconv" - "strings" - "testing" -) - -func TestNewQingStorageClassFromMap(t *testing.T) { - testcases := []struct { - name string - mp map[string]string - sc QingStorageClass - isError bool - strError string - }{ - { - name: "normal", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "10", - "stepSize": "10", - "fsType": "ext4", - "replica": "2", - }, - sc: QingStorageClass{ - VolumeType: 0, - VolumeMaxSize: 1000, - VolumeMinSize: 10, - VolumeStepSize: 10, - VolumeFsType: server.FileSystemExt4, - VolumeReplica: server.DefaultReplica, - }, - isError: false, - strError: "", - }, - { - name: "default storageclass", - mp: map[string]string{}, - sc: *NewDefaultQingStorageClassFromType(server.SSDEnterpriseDiskType), - isError: false, - strError: "", - }, - { - name: "type is string", - mp: map[string]string{ - "type": "k", - "maxSize": "1000", - "minSize": "10", - "stepSize": "10", - "fsType": "xfs", - "replica": "1", - }, - isError: true, - strError: "strconv.Atoi: parsing", - }, - { - name: "size is string", - mp: map[string]string{ - "type": "0", - "maxSize": "s", - "minSize": "10", - "stepSize": "10", - "fsType": "xfs", - }, - sc: QingStorageClass{}, - isError: true, - strError: "strconv.Atoi: parsing", - }, - { - name: "max size less than min size", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "1001", - "stepSize": "10", - "fsType": "ext3", - }, - sc: QingStorageClass{}, - isError: true, - strError: "volume maxSize must greater than or equal to volume minSize", - }, - { - name: "max size equal to min size", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "1000", - "stepSize": "10", - "fsType": "ext4", - "replica": "1", - }, - sc: QingStorageClass{ - VolumeType: 0, - VolumeMaxSize: 1000, - VolumeMinSize: 1000, - VolumeStepSize: 10, - VolumeFsType: server.FileSystemExt4, - VolumeReplica: server.SingleReplica, - }, - isError: false, - strError: "", - }, - { - name: "size less than zero", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "-2", - "stepSize": "10", - "fsType": "ext4", - }, - sc: QingStorageClass{}, - isError: true, - strError: "MinSize must not less than zero", - }, - { - name: "step size equal to zero", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "200", - "stepSize": "0", - "fsType": "ext4", - }, - sc: QingStorageClass{}, - isError: true, - strError: "StepSize must greate than zero", - }, - { - name: "input empty fsType", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "1000", - "stepSize": "2", - "fsType": "", - }, - sc: QingStorageClass{ - VolumeType: 0, - VolumeMaxSize: 1000, - VolumeMinSize: 1000, - VolumeStepSize: 2, - }, - isError: true, - strError: "unsupported fsType ", - }, - { - name: "set default fsType", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "1000", - "stepSize": "2", - }, - sc: QingStorageClass{ - VolumeType: 0, - VolumeMaxSize: 1000, - VolumeMinSize: 1000, - VolumeStepSize: 2, - VolumeFsType: server.FileSystemDefault, - VolumeReplica: server.DefaultReplica, - }, - isError: false, - strError: "", - }, - { - name: "input wrong fsType", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "1000", - "fsType": "wrong", - }, - sc: QingStorageClass{}, - isError: true, - strError: "unsupported fsType wrong", - }, - { - name: "not input fsType", - mp: map[string]string{ - "type": "0", - "maxSize": "1000", - "minSize": "1000", - }, - sc: QingStorageClass{ - VolumeType: 0, - VolumeMaxSize: 2000, - VolumeMinSize: 10, - VolumeStepSize: 10, - VolumeFsType: server.FileSystemDefault, - VolumeReplica: server.MultiReplica, - }, - isError: false, - strError: "", - }, - { - name: "only input volume type", - mp: map[string]string{ - "type": strconv.Itoa(server.NeonSANDiskType), - }, - sc: *NewDefaultQingStorageClassFromType(server.NeonSANDiskType), - isError: false, - strError: "", - }, - { - name: "input custom parameter", - mp: map[string]string{ - "type": strconv.Itoa(server.NeonSANDiskType), - "maxSize": "500", - "minSize": "100", - "stepSize": "100", - "fsType": "xfs", - "replica": "1", - }, - sc: QingStorageClass{ - VolumeType: server.NeonSANDiskType, - VolumeMaxSize: 500, - VolumeMinSize: 100, - VolumeStepSize: 100, - VolumeFsType: server.FileSystemXfs, - VolumeReplica: server.SingleReplica, - }, - isError: false, - strError: "", - }, - } - for _, v := range testcases { - res, err := NewQingStorageClassFromMap(v.mp) - if err != nil { - if v.isError == false { - t.Errorf("name %s: expect %t, actually false [%s]", v.name, v.isError, err.Error()) - } else if v.isError == true && !strings.Contains(err.Error(), v.strError) { - t.Errorf("name %s: expect [%s], actually [%s]", v.name, v.strError, err.Error()) - } - } else if !reflect.DeepEqual(*res, v.sc) { - t.Errorf("name %s: sc does not equal, expect [%v], actually [%v]", v.name, v.sc, res) - } - } -} - -func TestFormatVolumeSize(t *testing.T) { - testcases := []struct { - name string - sc QingStorageClass - size int - result int - }{ - { - name: "normal sc, normal size", - sc: QingStorageClass{ - VolumeMinSize: 10, - VolumeMaxSize: 500, - VolumeStepSize: 10, - }, - size: 24, - result: 30, - }, - { - name: "normal sc, size less than zero", - sc: QingStorageClass{ - VolumeMinSize: 10, - VolumeMaxSize: 500, - VolumeStepSize: 10, - }, - size: -1, - result: 10, - }, - { - name: "normal sc, size less than min size", - sc: QingStorageClass{ - VolumeMinSize: 10, - VolumeMaxSize: 500, - VolumeStepSize: 10, - }, - size: 8, - result: 10, - }, - { - name: "normal sc, size equal to max size", - sc: QingStorageClass{ - VolumeMinSize: 10, - VolumeMaxSize: 500, - VolumeStepSize: 10, - }, - size: 500, - result: 500, - }, - { - name: "normal sc, size greater than max size", - sc: QingStorageClass{ - VolumeMinSize: 10, - VolumeMaxSize: 500, - VolumeStepSize: 10, - }, - size: 501, - result: 500, - }, - { - name: "equal sc, size less than min size 1", - sc: QingStorageClass{ - VolumeMinSize: 502, - VolumeMaxSize: 502, - VolumeStepSize: 10, - }, - size: 23, - result: 502, - }, - { - name: "step size is 100", - sc: QingStorageClass{ - VolumeMinSize: 100, - VolumeMaxSize: 6000, - VolumeStepSize: 100, - }, - size: 443, - result: 500, - }, - { - name: "step size is 50", - sc: QingStorageClass{ - VolumeMinSize: 100, - VolumeMaxSize: 6000, - VolumeStepSize: 50, - }, - size: 433, - result: 450, - }, - } - for _, v := range testcases { - res := v.sc.FormatVolumeSize(v.size, v.sc.VolumeStepSize) - if res != v.result { - t.Errorf("name %s, expect %d, but actually %d", v.name, v.result, res) - } - } -} diff --git a/pkg/server/types.go b/pkg/server/types.go deleted file mode 100644 index ad89e891..00000000 --- a/pkg/server/types.go +++ /dev/null @@ -1,97 +0,0 @@ -package server - -import "time" - -const ( - // In Qingcloud bare host, the path of the file containing instance id. - InstanceFilePath = "/etc/qingcloud/instance-id" - RetryString = "please try later" - Int64Max = int64(^uint64(0) >> 1) - WaitInterval = 10 * time.Second - OperationWaitTimeout = 180 * time.Second -) - -const ( - Kib int64 = 1024 - Mib int64 = Kib * 1024 - Gib int64 = Mib * 1024 - Gib100 int64 = Gib * 100 - Tib int64 = Gib * 1024 - Tib100 int64 = Tib * 100 -) - -const ( - FileSystemExt3 string = "ext3" - FileSystemExt4 string = "ext4" - FileSystemXfs string = "xfs" - FileSystemDefault string = FileSystemExt4 -) - -const ( - SingleReplica int = 1 - MultiReplica int = 2 - DefaultReplica int = MultiReplica -) - -const ( - QingCloudSingleReplica string = "rpp-00000001" - QingCloudMultiReplica string = "rpp-00000002" -) - -var QingCloudReplName = map[int]string{ - 1: QingCloudSingleReplica, - 2: QingCloudMultiReplica, -} - -type ServerConfig struct { - instanceId string - configFilePath string - maxVolumePerNode int64 -} - -const ( - HighPerformanceDiskType int = 0 - HighCapacityDiskType int = 2 - SuperHighPerformanceDiskType int = 3 - StandardDiskType int = 100 - SSDEnterpriseDiskType int = 200 - NeonSANDiskType int = 5 -) - -// convert volume type to string -// https://docs.qingcloud.com/product/api/action/volume/create_volumes.html -var VolumeTypeToString = map[int]string{ - 0: "HighPerformance", - 2: "HighCapacity", - 3: "SuperHighPerformance", - 100: "Standard", - 200: "SSDEnterprise", - 5: "NeonSAN", -} - -var VolumeTypeToStepSize = map[int]int{ - 0: 10, - 2: 50, - 3: 10, - 100: 10, - 200: 10, - 5: 100, -} - -var VolumeTypeToMinSize = map[int]int{ - 0: 10, - 2: 100, - 3: 10, - 100: 10, - 200: 10, - 5: 100, -} - -var VolumeTypeToMaxSize = map[int]int{ - 0: 2000, - 2: 5000, - 3: 2000, - 100: 2000, - 200: 2000, - 5: 50000, -} diff --git a/pkg/server/util.go b/pkg/server/util.go deleted file mode 100644 index 0cafa6f9..00000000 --- a/pkg/server/util.go +++ /dev/null @@ -1,239 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 server - -import ( - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/golang/glog" - qcconfig "github.com/yunify/qingcloud-sdk-go/config" - "io/ioutil" - "os" - "strings" - "time" -) - -// NewServerConfig create ServerConfig object to get server config -func NewServerConfig(id string, filePath string, volumeNumber int64) *ServerConfig { - sc := &ServerConfig{ - instanceId: id, - configFilePath: filePath, - maxVolumePerNode: volumeNumber, - } - // If instance file existed, plugin SHOULD get instance id - // from instance file (/etc/qingcloud/instance-id). - if _, err := os.Stat(InstanceFilePath); !os.IsNotExist(err) { - sc.readInstanceId() - } - return sc -} - -// GetConfigFilePath get config file path -func (cfg *ServerConfig) GetConfigFilePath() string { - return cfg.configFilePath -} - -// GetMaxVolumePerNode gets maximum number of volumes that controller can publish to the node -func (cfg *ServerConfig) GetMaxVolumePerNode() int64 { - return cfg.maxVolumePerNode -} - -// GetCurrentInstanceId gets instance id -func (cfg *ServerConfig) GetInstanceId() string { - return cfg.instanceId -} - -func (cfg *ServerConfig) readInstanceId() { - bytes, err := ioutil.ReadFile(InstanceFilePath) - if err != nil { - glog.Errorf("Getting instance-id error: %s", err.Error()) - os.Exit(1) - } - cfg.instanceId = string(bytes[:]) - cfg.instanceId = strings.Replace(cfg.instanceId, "\n", "", -1) - glog.Infof("Getting instance-id: \"%s\"", cfg.instanceId) -} - -// ReadConfigFromFile -// Read config file from a path and return config -func ReadConfigFromFile(filePath string) (*qcconfig.Config, error) { - config, err := qcconfig.NewDefault() - if err != nil { - return nil, err - } - if err = config.LoadConfigFromFilepath(filePath); err != nil { - return nil, err - } - return config, nil -} - -// ContainsVolumeCapability -// Does Array of VolumeCapability_AccessMode contain the volume capability of subCaps -func ContainsVolumeCapability(accessModes []*csi.VolumeCapability_AccessMode, subCaps *csi.VolumeCapability) bool { - for _, cap := range accessModes { - if cap.GetMode() == subCaps.GetAccessMode().GetMode() { - return true - } - } - return false -} - -// ContainsVolumeCapabilities -// Does array of VolumeCapability_AccessMode contain volume capabilities of subCaps -func ContainsVolumeCapabilities(accessModes []*csi.VolumeCapability_AccessMode, subCaps []*csi.VolumeCapability) bool { - for _, v := range subCaps { - if !ContainsVolumeCapability(accessModes, v) { - return false - } - } - return true -} - -// ContainsNodeServiceCapability -// Does array of NodeServiceCapability contain node service capability of subCap -func ContainsNodeServiceCapability(nodeCaps []*csi.NodeServiceCapability, subCap csi.NodeServiceCapability_RPC_Type) bool { - for _, v := range nodeCaps { - if strings.Contains(v.String(), subCap.String()) { - return true - } - } - return false -} - -// GibToByte -// Convert GiB to Byte -func GibToByte(num int) int64 { - if num < 0 { - return 0 - } - return int64(num) * Gib -} - -// ByteCeilToGib -// Convert Byte to Gib -func ByteCeilToGib(num int64) int { - if num <= 0 { - return 0 - } - res := num / Gib - if res*Gib < num { - res += 1 - } - return int(res) -} - -// Check replica -// Support: 2 MultiReplicas, 1 SingleReplica -func IsValidReplica(replica int) bool { - switch replica { - case MultiReplica: - return true - case SingleReplica: - return true - default: - return false - } -} - -// Check file system type -// Support: ext3, ext4 and xfs -func IsValidFileSystemType(fs string) bool { - switch fs { - case FileSystemExt3: - return true - case FileSystemExt4: - return true - case FileSystemXfs: - return true - default: - return false - } -} - -// Check volume type -func IsValidVolumeType(volumeType int) bool { - if _, ok := VolumeTypeToString[volumeType]; ok { - return true - } - return false -} - -// EntryFunction print timestamps -func EntryFunction(functionName string) func() { - start := time.Now() - glog.Infof("*************** enter %s at %s ***************", functionName, start.String()) - return func() { - glog.Infof("=============== exit %s (%s since %s) ===============", functionName, time.Since(start), - start.String()) - } -} - -// FormatVolumeSize transfer to proper volume size -func FormatVolumeSize(volType int, volSize int) int { - _, ok := VolumeTypeToString[volType] - if ok == false { - return -1 - } - volTypeMinSize := VolumeTypeToMinSize[volType] - volTypeMaxSize := VolumeTypeToMaxSize[volType] - volTypeStepSize := VolumeTypeToStepSize[volType] - if volSize <= volTypeMinSize { - return volTypeMinSize - } else if volSize >= volTypeMaxSize { - return volTypeMaxSize - } - if volSize%volTypeStepSize != 0 { - volSize = (volSize/volTypeStepSize + 1) * volTypeStepSize - } - if volSize >= volTypeMaxSize { - return volTypeMaxSize - } - return volSize -} - -// Get minimal required bytes in capacity range -// Return Values: -// -1 represent cannot get min required bytes -func GetMinRequiredBytes(requiredBytes, limitBytes []int64) int64 { - res := int64(0) - for _, v := range requiredBytes { - if res < v { - res = v - } - } - for _, v := range limitBytes { - if res > v { - return -1 - } - } - return res -} - -// Valid capacity bytes in capacity range -func IsValidCapacityBytes(cur int64, requiredBytes, limitBytes []int64) bool { - res := cur - for _, v := range requiredBytes { - if res < v { - return false - } - } - for _, v := range limitBytes { - if res > v { - return false - } - } - return true -} diff --git a/pkg/server/util_test.go b/pkg/server/util_test.go deleted file mode 100644 index 884bbbac..00000000 --- a/pkg/server/util_test.go +++ /dev/null @@ -1,548 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 server - -import ( - "github.com/container-storage-interface/spec/lib/go/csi" - "testing" -) - -func TestContainsVolumeCapability(t *testing.T) { - tests := []struct { - name string - accessModes []*csi.VolumeCapability_AccessMode - capabilities *csi.VolumeCapability - result bool - }{ - { - name: "Driver: SINGLE_NODE_WRITER, Req: SINGLE_NODE_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - }, - capabilities: &csi.VolumeCapability{ - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}}, - result: true, - }, - { - name: "Driver: SINGLE_NODE_WRITER, Req: MULTI_NODE_MULTI_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - }, - capabilities: &csi.VolumeCapability{ - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}}, - result: false, - }, - { - name: "Driver: SINGLE_NODE_WRITER, MULTI_NODE_MULTI_WRITER, Req: MULTI_NODE_MULTI_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}, - }, - capabilities: &csi.VolumeCapability{ - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}}, - result: true, - }, - { - name: "Driver: MULTI_NODE_MULTI_WRITER, MULTI_NODE_READER_ONLY, Req: MULTI_NODE_READER_ONLY", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}, - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY}, - }, - capabilities: &csi.VolumeCapability{ - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY}}, - result: true, - }, - { - name: "Driver: MULTI_NODE_READER_ONLY, Req: SINGLE_NODE_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY}, - }, - capabilities: &csi.VolumeCapability{ - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}}, - result: false, - }, - } - for _, v := range tests { - res := ContainsVolumeCapability(v.accessModes, v.capabilities) - if res != v.result { - t.Errorf("test %s: expect %t, but result was %t", v.name, v.result, res) - } - } -} - -func TestContainsVolumeCapabilities(t *testing.T) { - tests := []struct { - name string - accessModes []*csi.VolumeCapability_AccessMode - capabilities []*csi.VolumeCapability - result bool - }{ - { - name: "Driver: SINGLE_NODE_WRITER, Req: SINGLE_NODE_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - }, - capabilities: []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}}, - }, - result: true, - }, - { - name: "Driver: SINGLE_NODE_WRITER, Req: MULTI_NODE_MULTI_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - }, - capabilities: []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}}, - }, - }, - { - name: "Driver: SINGLE_NODE_WRITER, Req: MULTI_NODE_READER_ONLY", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - }, - capabilities: []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY}}, - }, - }, - { - name: "Driver: SINGLE_NODE_WRITER, MULTI_NODE_MULTI_WRITER, Req: MULTI_NODE_MULTI_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}, - }, - capabilities: []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}}, - }, - result: true, - }, - { - name: "Driver: MULTI_NODE_MULTI_WRITER, MULTI_NODE_READER_ONLY, Req: MULTI_NODE_READER_ONLY", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}, - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY}, - }, - capabilities: []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY}}, - }, - result: true, - }, - { - name: "Driver: MULTI_NODE_READER_ONLY, Req: SINGLE_NODE_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY}, - }, - capabilities: []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}}, - }, - result: false, - }, - { - name: "Driver: SINGLE_NODE_WRITER, Req: SINGLE_NODE_WRITER,MULTI_NODE_READER_ONLY", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - }, - capabilities: []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}}, - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY}}, - }, - result: false, - }, - { - name: "Driver: SINGLE_NODE_WRITER, MULTI_NODE_WRITER, Req: MULTI_NODE_MULTI_WRITER, SINGLE_NODE_WRITER", - accessModes: []*csi.VolumeCapability_AccessMode{ - { - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}, - { - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}, - }, - capabilities: []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER}}, - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER}}, - }, - result: true, - }, - } - for _, v := range tests { - res := ContainsVolumeCapabilities(v.accessModes, v.capabilities) - if res != v.result { - t.Errorf("test %s: expect %t, but result was %t", v.name, v.result, res) - } - } -} - -func TestContainsNodeServiceCapability(t *testing.T) { - tests := []struct { - name string - nodeCaps []*csi.NodeServiceCapability - subCap csi.NodeServiceCapability_RPC_Type - result bool - }{ - { - name: "Node Caps: STAGE_UNSTAGE, ", - nodeCaps: []*csi.NodeServiceCapability{ - { - Type: &csi.NodeServiceCapability_Rpc{ - Rpc: &csi.NodeServiceCapability_RPC{ - Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, - }, - }, - }, - }, - subCap: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, - result: true, - }, - { - name: "Node Caps: STAGE_UNSTAGE, ", - nodeCaps: []*csi.NodeServiceCapability{ - { - Type: &csi.NodeServiceCapability_Rpc{ - Rpc: &csi.NodeServiceCapability_RPC{ - Type: csi.NodeServiceCapability_RPC_UNKNOWN, - }, - }, - }, - }, - subCap: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, - result: false, - }, - } - for _, v := range tests { - res := ContainsNodeServiceCapability(v.nodeCaps, v.subCap) - if res != v.result { - t.Errorf("test %s: expect %t, but result was %t", v.name, v.result, res) - } - } -} - -func TestGibToByte(t *testing.T) { - testcases := []struct { - name string - gb int - byte int64 - }{ - {"-1Gb", -1, 0}, - {"0Gb", 0, 0}, - {"1GB", 1, Gib}, - {"10GB", 10, 10 * Gib}, - {"100GB", 100, 100 * Gib}, - {"1000GB", 1000, 1000 * Gib}, - } - - for _, v := range testcases { - res := GibToByte(v.gb) - if res != v.byte { - t.Errorf("test %s: expect %d, but result was %d", v.name, v.byte, res) - } - } -} - -func TestByteCeilToGib(t *testing.T) { - testcases := []struct { - name string - byte int64 - gb int - }{ - {"-1 Byte", -1, 0}, - {"0 Byte", 0, 0}, - {"1 Byte", 1, 1}, - {"1 Gib - 1 Byte", Gib - 1, 1}, - {"1 Gib + 1 Byte", Gib + 1, 2}, - {"10 Gib - 1 Byte", 10*Gib - 1, 10}, - {"10 Gib", 10 * Gib, 10}, - {"10 Gib + 1024 Byte", 10*Gib + Kib, 11}, - {"99 Gib - 1 Mib", 99*Gib - Mib, 99}, - {"99 Gib + 1 Mib", 99*Gib + Mib, 100}, - } - - for _, v := range testcases { - res := ByteCeilToGib(v.byte) - if res != v.gb { - t.Errorf("test %s: expect %d Gb, but actually %d", v.name, v.gb, res) - } - } -} - -func TestIsValidFileSystemType(t *testing.T) { - testcases := []struct { - name string - fsType string - expect bool - }{ - { - name: "EXT3", - fsType: FileSystemExt3, - expect: true, - }, - { - name: "EXT4", - fsType: FileSystemExt4, - expect: true, - }, - { - name: "XFS", - fsType: FileSystemXfs, - expect: true, - }, - { - name: "ext5", - fsType: "ext5", - expect: false, - }, - { - name: "NTFS", - fsType: "NTFS", - expect: false, - }, - } - - for _, v := range testcases { - res := IsValidFileSystemType(v.fsType) - if res != v.expect { - t.Errorf("test %s: expect %t, but actually %t", v.name, v.expect, res) - } - } -} - -func TestIsValidReplica(t *testing.T) { - testcases := []struct { - name string - replica int - expect bool - }{ - { - name: "single", - replica: SingleReplica, - expect: true, - }, - { - name: "multi", - replica: MultiReplica, - expect: true, - }, - { - name: "fake1", - replica: 0, - expect: false, - }, - { - name: "fake2", - replica: 3, - expect: false, - }, - } - - for _, v := range testcases { - res := IsValidReplica(v.replica) - if res != v.expect { - t.Errorf("test %s: expect %t, but actually %t", v.name, v.expect, res) - } - } -} - -func TestFormatVolumeSize(t *testing.T) { - testcase := []struct { - name string - inType int - inSize int - outSize int - }{ - { - name: "normal size", - inType: 0, - inSize: 20, - outSize: 20, - }, - { - name: "format size 1", - inType: 200, - inSize: 123, - outSize: 130, - }, - { - name: "format size 2", - inType: 2, - inSize: 123, - outSize: 150, - }, - { - name: "format size 3", - inType: 5, - inSize: 123, - outSize: 200, - }, - { - name: "less than min size", - inType: 5, - inSize: 20, - outSize: VolumeTypeToMinSize[5], - }, - { - name: "more than max size", - inType: 2, - inSize: 9999, - outSize: VolumeTypeToMaxSize[2], - }, - { - name: "type not found", - inType: 1, - inSize: 30, - outSize: -1, - }, - } - for _, o := range testcase { - resSize := FormatVolumeSize(o.inType, o.inSize) - if resSize != o.outSize { - t.Errorf("name %s: expect %d, but actually %d", o.name, o.outSize, resSize) - } - - } -} - -func TestGetMinRequiredBytes(t *testing.T) { - testcase := []struct { - name string - requiredBytes []int64 - limitBytes []int64 - result int64 - }{ - { - name: "normal range", - requiredBytes: []int64{123, 345}, - limitBytes: []int64{345, 534}, - result: 345, - }, - { - name: "bad result", - requiredBytes: []int64{-1, 345}, - limitBytes: []int64{234}, - result: -1, - }, - { - name: "zero value", - requiredBytes: []int64{}, - limitBytes: []int64{213}, - result: 0, - }, - } - for _, v := range testcase { - res := GetMinRequiredBytes(v.requiredBytes, v.limitBytes) - if v.result != res { - t.Errorf("name %s: expect %d but actually %d", v.name, v.result, res) - } - } -} - -func TestIsValidCapacityBytes(t *testing.T) { - testcases := []struct { - name string - curBytes int64 - requiredBytes []int64 - limitBytes []int64 - result bool - }{ - { - name: "normal range", - curBytes: 347, - requiredBytes: []int64{123, 345}, - limitBytes: []int64{360, 534}, - result: true, - }, - { - name: "edge result", - curBytes: 345, - requiredBytes: []int64{-1, 345}, - limitBytes: []int64{345}, - result: true, - }, - { - name: "bad result", - curBytes: 345, - requiredBytes: []int64{-1, 390}, - limitBytes: []int64{345}, - result: false, - }, - { - name: "zero value", - curBytes: 100, - requiredBytes: []int64{}, - limitBytes: []int64{213}, - result: true, - }, - { - name: "big value", - curBytes: 107374182400, - requiredBytes: []int64{107374182400}, - limitBytes: []int64{9223372036854775807}, - result: true, - }, - { - name: "big same value", - curBytes: 10737418240, - requiredBytes: []int64{10737418240}, - limitBytes: []int64{10737418240}, - result: true, - }, - } - for _, v := range testcases { - res := IsValidCapacityBytes(v.curBytes, v.requiredBytes, v.limitBytes) - if v.result != res { - t.Errorf("name %s: expect %t but actually %t", v.name, v.result, res) - } - } -} diff --git a/pkg/server/volume/volume_manager.go b/pkg/server/volume/volume_manager.go deleted file mode 100644 index 800d3a19..00000000 --- a/pkg/server/volume/volume_manager.go +++ /dev/null @@ -1,435 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 volume - -import ( - "fmt" - "github.com/golang/glog" - "github.com/yunify/qingcloud-csi/pkg/server" - "github.com/yunify/qingcloud-csi/pkg/server/storageclass" - qcclient "github.com/yunify/qingcloud-sdk-go/client" - qcconfig "github.com/yunify/qingcloud-sdk-go/config" - qcservice "github.com/yunify/qingcloud-sdk-go/service" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -const ( - DiskStatusPending string = "pending" - DiskStatusAvailable string = "available" - DiskStatusInuse string = "in-use" - DiskStatusSuspended string = "suspended" - DiskStatusDeleted string = "deleted" - DiskStatusCeased string = "ceased" -) - -type VolumeManager interface { - FindVolume(id string) (volume *qcservice.Volume, err error) - FindVolumeByName(name string) (volume *qcservice.Volume, err error) - CreateVolume(volumeName string, requestSize int, sc storageclass.QingStorageClass) (volumeId string, err error) - CreateVolumeFromSnapshot(volumeName string, snapshotId string) (volumeId string, err error) - DeleteVolume(id string) error - IsAttachedToInstance(volumeId string, instanceId string) (flag bool, err error) - AttachVolume(volumeId string, instanceId string) error - DetachVolume(volumeId string, instanceId string) error - ResizeVolume(volumeId string, requestSize int) error - GetZone() string - waitJob(jobId string) error -} - -type volumeManager struct { - volumeService *qcservice.VolumeService - snapshotService *qcservice.SnapshotService - jobService *qcservice.JobService -} - -// NewVolumeManagerFromConfig -// Create volume manager from config -func NewVolumeManagerFromConfig(config *qcconfig.Config) (VolumeManager, error) { - // initial qingcloud iaas service - qs, err := qcservice.Init(config) - if err != nil { - return nil, err - } - // create volume service - vs, _ := qs.Volume(config.Zone) - // create snapshot service - ss, _ := qs.Snapshot(config.Zone) - // create job service - js, _ := qs.Job(config.Zone) - // initial volume manager - vm := volumeManager{ - volumeService: vs, - snapshotService: ss, - jobService: js, - } - glog.Infof("Finished initial volume manager") - return &vm, nil -} - -// NewVolumeManagerFromFile -// Create volume manager from file -func NewVolumeManagerFromFile(filePath string) (VolumeManager, error) { - config, err := server.ReadConfigFromFile(filePath) - if err != nil { - glog.Errorf("Failed read config file [%s], error: [%s]", filePath, err.Error()) - return nil, err - } - glog.Infof("Succeed read config file [%s]", filePath) - return NewVolumeManagerFromConfig(config) -} - -// Find volume by volume ID -// Return: nil, nil: not found volumes -// volume, nil: found volume -// nil, error: internal error -func (vm *volumeManager) FindVolume(id string) (volume *qcservice.Volume, err error) { - // Set DescribeVolumes input - input := qcservice.DescribeVolumesInput{} - input.Volumes = append(input.Volumes, &id) - // Call describe volume - output, err := vm.volumeService.DescribeVolumes(&input) - // Error: - // 1. Error is not equal to nil. - if err != nil { - return nil, err - } - // 2. Return code is not equal to 0. - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return nil, fmt.Errorf("Call IaaS DescribeVolumes err: volume id %s in %s", id, vm.volumeService.Config.Zone) - } - switch *output.TotalCount { - // Not found volumes - case 0: - return nil, nil - // Found one volume - case 1: - if *output.VolumeSet[0].Status == DiskStatusCeased || *output.VolumeSet[0]. - Status == DiskStatusDeleted { - return nil, nil - } - return output.VolumeSet[0], nil - // Found duplicate volumes - default: - return nil, - fmt.Errorf("Call IaaS DescribeVolumes err: find duplicate volumes, volume id %s in %s", id, vm.volumeService.Config.Zone) - } -} - -// Find volume by volume name -// In Qingcloud IaaS platform, it is possible that two volumes have the same name. -// In Kubernetes, the CO will set a unique PV name. -// CSI driver take the PV name as a volume name. -// Return: nil, nil: not found volumes -// volumes, nil: found volume -// nil, error: internal error -func (vm *volumeManager) FindVolumeByName(name string) (volume *qcservice.Volume, err error) { - if len(name) == 0 { - return nil, nil - } - // Set input arguments - input := qcservice.DescribeVolumesInput{} - input.SearchWord = &name - // Call DescribeVolumes - output, err := vm.volumeService.DescribeVolumes(&input) - // Handle error - if err != nil { - return nil, err - } - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return nil, fmt.Errorf("Call IaaS DescribeVolumes err: volume name %s in %s", name, vm.volumeService.Config.Zone) - } - // Not found volumes - for _, v := range output.VolumeSet { - if *v.VolumeName != name { - continue - } - if *v.Status == DiskStatusCeased || *v.Status == DiskStatusDeleted { - continue - } - return v, nil - } - return nil, nil -} - -// CreateVolume -// 1. format volume size -// 2. create volume -// 3. wait job -func (vm *volumeManager) CreateVolume(volumeName string, requestSize int, sc storageclass.QingStorageClass) (volumeId string, - err error) { - // 0. Set CreateVolume args - // create volume count - count := 1 - // volume replicas - replica := server.QingCloudReplName[sc.VolumeReplica] - // volume provisioner size - size := sc.FormatVolumeSize(requestSize, sc.VolumeStepSize) - // set input value - input := &qcservice.CreateVolumesInput{ - Count: &count, - Repl: &replica, - Size: &size, - VolumeName: &volumeName, - VolumeType: &sc.VolumeType, - } - // 1. Create volume - glog.Infof("Call IaaS CreateVolume request size: %d GB, zone: %s, type: %d, count: %d, replica: %s, name: %s", - *input.Size, *vm.volumeService.Properties.Zone, *input.VolumeType, *input.Count, *input.Repl, *input.VolumeName) - output, err := vm.volumeService.CreateVolumes(input) - if err != nil { - return "", err - } - // wait job - glog.Infof("Call IaaS WaitJob %s", *output.JobID) - if err := vm.waitJob(*output.JobID); err != nil { - return "", err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return "", fmt.Errorf(*output.Message) - } - volumeId = *output.Volumes[0] - glog.Infof("Call IaaS CreateVolume name %s id %s succeed", volumeName, volumeId) - return *output.Volumes[0], nil -} - -// CreateVolumeFromSnapshot -// In QingCloud, the volume size created from snapshot is equal to original volume. -func (vm *volumeManager) CreateVolumeFromSnapshot(volumeName string, snapshotId string) (volumeId string, err error) { - input := &qcservice.CreateVolumeFromSnapshotInput{ - VolumeName: &volumeName, - Snapshot: &snapshotId, - } - glog.Infof("Call IaaS CreateVolumeFromSnapshot request volume name: %s, snapshot id: %s\n", - *input.VolumeName, *input.Snapshot) - output, err := vm.snapshotService.CreateVolumeFromSnapshot(input) - if err != nil { - return "", err - } - // wait job - glog.Infof("Call IaaS WaitJob %s", *output.JobID) - if err := vm.waitJob(*output.JobID); err != nil { - return "", err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return "", fmt.Errorf(*output.Message) - } - glog.Infof("Call IaaS CreateVolumeFromSnapshot succeed, volume id %s", *output.VolumeID) - return *output.VolumeID, nil -} - -// DeleteVolume -// 1. delete volume by id -// 2. wait job -func (vm *volumeManager) DeleteVolume(id string) error { - // set input value - input := &qcservice.DeleteVolumesInput{} - input.Volumes = append(input.Volumes, &id) - // delete volume - glog.Infof("Call IaaS DeleteVolume request id: %s, zone: %s", - id, *vm.volumeService.Properties.Zone) - output, err := vm.volumeService.DeleteVolumes(input) - if err != nil { - return err - } - // wait job - glog.Infof("Call IaaS WaitJob %s", *output.JobID) - if err := vm.waitJob(*output.JobID); err != nil { - return err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return fmt.Errorf(*output.Message) - } - glog.Infof("Call IaaS DeleteVolume %s succeed", id) - return nil -} - -// IsAttachedToInstance -// 1. get volume information -// 2. compare input instance id with instance field in volume information -func (vm *volumeManager) IsAttachedToInstance(volumeId string, instanceId string) (flag bool, err error) { - // zone - zone := vm.volumeService.Config.Zone - - // get volume item - volumeItem, err := vm.FindVolume(volumeId) - if err != nil { - return false, status.Errorf(codes.Internal, err.Error()) - } - // check volume exist - if volumeItem == nil { - return false, status.Errorf( - codes.NotFound, "Volume %s not found in %s", volumeId, zone) - } - - if volumeItem.Instance != nil && *volumeItem.Instance.InstanceID == instanceId { - return true, nil - } - return false, nil -} - -// AttachVolume -// 1. get volume information -// 2. attach volume on instance -// 3. wait job -func (vm *volumeManager) AttachVolume(volumeId string, instanceId string) error { - zone := *vm.volumeService.Properties.Zone - // check volume status - vol, err := vm.FindVolume(volumeId) - if err != nil { - return err - } - if vol == nil { - return fmt.Errorf("Cannot found volume %s", volumeId) - } - if *vol.Instance.InstanceID == "" { - // set input parameter - input := &qcservice.AttachVolumesInput{} - input.Volumes = append(input.Volumes, &volumeId) - input.Instance = &instanceId - // attach volume - glog.Infof("Call IaaS AttachVolume request volume id: %s, instance id: %s, zone: %s", volumeId, instanceId, zone) - output, err := vm.volumeService.AttachVolumes(input) - if err != nil { - return err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return fmt.Errorf(*output.Message) - } - // wait job - glog.Infof("Call IaaS WaitJob %s", *output.JobID) - return vm.waitJob(*output.JobID) - } else { - if *vol.Instance.InstanceID == instanceId { - return nil - } - return fmt.Errorf("Volume %s has been attached to another instance %s.", volumeId, *vol.Instance.InstanceID) - } -} - -// detach volume -// 1. get volume information -// 2. If volume not attached, return nil. -// If volume attached, check instance id. -// 3. attach volume -// 4. wait job -func (vm *volumeManager) DetachVolume(volumeId string, instanceId string) error { - zone := *vm.volumeService.Properties.Zone - // check volume status - vol, err := vm.FindVolume(volumeId) - if err != nil { - return err - } - if vol == nil { - return fmt.Errorf("Cannot found volume %s", volumeId) - } - if *vol.Instance.InstanceID == "" { - return nil - } else { - if *vol.Instance.InstanceID == instanceId || instanceId == "" { - // set input parameter - input := &qcservice.DetachVolumesInput{} - input.Volumes = append(input.Volumes, &volumeId) - input.Instance = vol.Instance.InstanceID - // attach volume - glog.Infof("Call IaaS DetachVolume request volume id: %s, instance id: %s, zone: %s", volumeId, instanceId, zone) - output, err := vm.volumeService.DetachVolumes(input) - if err != nil { - return err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return fmt.Errorf(*output.Message) - } - // wait job - glog.Infof("Call IaaS WaitJob %s", *output.JobID) - return vm.waitJob(*output.JobID) - } - return fmt.Errorf("Volume %s has been attached to another instance %s", volumeId, *vol.Instance.InstanceID) - } -} - -// ResizeVolume can expand the size of a volume offline -// requestSize: GB -func (vm *volumeManager) ResizeVolume(volumeId string, requestSize int) error { - zone := *vm.volumeService.Properties.Zone - // check volume status - vol, err := vm.FindVolume(volumeId) - if err != nil { - return err - } - if vol == nil { - return fmt.Errorf("ResizeVolume: Cannot found volume %s", volumeId) - } - - // resize - glog.Infof("Call Iaas ResizeVolume request volume [%s], size [%d Gib] in zone [%s]", - volumeId, requestSize, zone) - input := &qcservice.ResizeVolumesInput{} - input.Size = &requestSize - input.Volumes = []*string{&volumeId} - output, err := vm.volumeService.ResizeVolumes(input) - if err != nil { - return err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return fmt.Errorf("ResizeVolume: " + *output.Message) - } - // wait job - glog.Infof("Call IaaS WaitJob %s", *output.JobID) - if err := vm.waitJob(*output.JobID); err != nil { - return err - } - // check output - if *output.RetCode != 0 { - glog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) - return fmt.Errorf("ResizeVolume: " + *output.Message) - } - glog.Infof("Call IaaS ResizeVolume id %s size %d succeed", volumeId, requestSize) - return nil -} - -// GetZone -// Get current zone in Qingcloud IaaS -func (vm *volumeManager) GetZone() string { - if vm == nil || vm.volumeService == nil || vm.volumeService.Properties == nil || vm.volumeService.Properties.Zone == nil { - return "" - } - return *vm.volumeService.Properties.Zone -} - -func (vm *volumeManager) waitJob(jobId string) error { - err := qcclient.WaitJob(vm.jobService, jobId, server.OperationWaitTimeout, server.WaitInterval) - if err != nil { - glog.Error("Call Iaas WaitJob: ", jobId) - return err - } - return nil -} diff --git a/pkg/server/volume/volume_manager_test.go b/pkg/server/volume/volume_manager_test.go deleted file mode 100644 index 3e6899a1..00000000 --- a/pkg/server/volume/volume_manager_test.go +++ /dev/null @@ -1,431 +0,0 @@ -// +------------------------------------------------------------------------- -// | Copyright (C) 2018 Yunify, Inc. -// +------------------------------------------------------------------------- -// | Licensed under the Apache License, Version 2.0 (the "License"); -// | you may not use this work except in compliance with the License. -// | You may obtain a copy of the License in the LICENSE file, or 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 volume - -import ( - "github.com/yunify/qingcloud-csi/pkg/server" - "github.com/yunify/qingcloud-csi/pkg/server/storageclass" - "runtime" - "testing" -) - -var ( - // Tester should set these variables before executing unit test. - volumeId1 string = "vol-8boq0cz6" - volumeName1 string = "qingcloud-csi-test" - instanceId1 string = "i-0nuxqgal" - instanceId2 string = "i-tta11nep" - resizeVolumeId string = "vol-tysu3tg2" - volFromSnapNorm string = "volFromSnapNorm" - snapshotId1 string = "ss-dmrxy2mn" -) - -var getvm = func() VolumeManager { - // get storage class - var filePath string - if runtime.GOOS == "linux" { - filePath = "/root/.qingcloud/config.yaml" - } - if runtime.GOOS == "darwin" { - filePath = "/etc/qingcloud/client.yaml" - } - vm, err := NewVolumeManagerFromFile(filePath) - if err != nil { - return nil - } - return vm -} - -func TestFindVolume(t *testing.T) { - vm := getvm() - // testcase - testcases := []struct { - name string - id string - result bool - }{ - { - name: "Available", - id: volumeId1, - result: true, - }, - { - name: "Not found", - id: volumeId1 + "fake", - result: false, - }, - { - name: "By name", - id: volumeName1, - result: false, - }, - } - - // test findVolume - for _, v := range testcases { - vol, err := vm.FindVolume(v.id) - if err != nil { - t.Error("find volume error: ", err.Error()) - } - res := vol != nil - if res != v.result { - t.Errorf("name: %s, expect %t, actually %t", v.name, v.result, res) - } - } -} - -func TestFindVolumeByName(t *testing.T) { - testcases := []struct { - name string - volume string - result bool - }{ - { - name: "Available", - volume: volumeName1, - result: true, - }, - { - name: "Ceased", - volume: "sanity", - result: false, - }, - { - name: "Volume id", - volume: volumeId1, - result: false, - }, - { - name: "Substring", - volume: string((volumeName1)[:2]), - result: false, - }, - { - name: "Null string", - volume: "", - result: false, - }, - } - - vm := getvm() - // test findVolume - for _, v := range testcases { - vol, err := vm.FindVolumeByName(v.volume) - if err != nil { - t.Error("find volume error: ", err.Error()) - } - res := vol != nil - if res != v.result { - t.Errorf("name %s, expect %t, actually %t", v.name, v.result, res) - } - } -} - -func TestCreateVolume(t *testing.T) { - sc := storageclass.NewDefaultQingStorageClass() - vm := getvm() - - testcases := []struct { - name string - volName string - reqSize int - storageClass storageclass.QingStorageClass - result bool - volId string - }{ - { - name: "create volume name test-1", - volName: "test-1", - reqSize: 1, - storageClass: *sc, - result: true, - volId: "", - }, - { - name: "create volume name test-1 repeatedly", - volName: "test-1", - reqSize: 3, - storageClass: *sc, - result: true, - volId: "", - }, - { - name: "create volume name test-2", - volName: "test-2", - reqSize: 20, - storageClass: *sc, - result: true, - volId: "", - }, - - { - name: "create volume name test-3 for single replica", - volName: "test-3", - reqSize: 20, - storageClass: storageclass.QingStorageClass{ - VolumeType: 100, - VolumeMaxSize: 500, - VolumeMinSize: 10, - VolumeStepSize: 10, - VolumeFsType: server.FileSystemDefault, - VolumeReplica: server.SingleReplica, - }, - result: true, - volId: "", - }, - } - for i, v := range testcases { - volId, err := vm.CreateVolume(v.volName, v.reqSize, v.storageClass) - if err != nil { - t.Errorf("test %s: %s", v.name, err.Error()) - } else { - testcases[i].volId = volId - vol, _ := vm.FindVolume(volId) - if *vol.VolumeName != v.volName { - t.Errorf("test %s: expect %t", v.name, v.result) - } - } - } - // clear process - for _, v := range testcases { - err := vm.DeleteVolume(v.volId) - if err != nil { - t.Errorf("test %s: delete error %s", v.name, err.Error()) - } - } -} - -func TestAttachVolume(t *testing.T) { - vm := getvm() - // testcase - testcases := []struct { - name string - volumeId string - instanceId string - isError bool - }{ - { - name: "Attach success", - volumeId: volumeId1, - instanceId: instanceId1, - isError: false, - }, - { - name: "Attach repeatedly, idempotent", - volumeId: volumeId1, - instanceId: instanceId1, - isError: false, - }, - { - name: "Attach another instance", - volumeId: volumeId1, - instanceId: instanceId2, - isError: true, - }, - { - name: "Attach not exist instance", - volumeId: volumeId1, - instanceId: "ins-123456", - isError: true, - }, - } - for _, v := range testcases { - err := vm.AttachVolume(v.volumeId, v.instanceId) - if err != nil && !v.isError { - t.Errorf("error name %s: %s", v.name, err.Error()) - } - } - -} - -func TestIsAttachedToInstance(t *testing.T) { - vm := getvm() - testcases := []struct { - name string - volumeId string - instanceId string - result bool - isError bool - }{ - { - name: "Attach success", - volumeId: volumeId1, - instanceId: instanceId1, - result: true, - isError: false, - }, - { - name: "Attach another instance", - volumeId: volumeId1, - instanceId: instanceId2, - result: false, - isError: false, - }, - { - name: "Not found volume", - volumeId: volumeId1 + "fake", - instanceId: instanceId1, - result: false, - isError: true, - }, - { - name: "Not found instance", - volumeId: volumeId1, - instanceId: instanceId1 + "fake", - result: false, - isError: false, - }, - } - for _, v := range testcases { - flag, err := vm.IsAttachedToInstance(v.volumeId, v.instanceId) - if err != nil { - if !v.isError { - t.Errorf("error name %s: %s", v.name, err.Error()) - } - } - if flag != v.result { - t.Errorf("name %s: expect %t", v.name, v.result) - } - } -} - -func TestDetachVolume(t *testing.T) { - vm := getvm() - testcases := []struct { - name string - volumeId string - instanceId string - isError bool - }{ - { - name: "detach normally", - volumeId: volumeId1, - instanceId: instanceId1, - isError: false, - }, - { - name: "detach repeatedly, idempotent", - volumeId: volumeId1, - instanceId: instanceId1, - isError: false, - }, - { - name: "volume not found", - volumeId: "fake", - instanceId: instanceId1, - isError: true, - }, - { - name: "instance not found", - volumeId: volumeId1, - instanceId: "fake", - isError: true, - }, - } - - for _, v := range testcases { - err := vm.DetachVolume(v.volumeId, v.instanceId) - if err != nil && !v.isError { - t.Errorf("error name %s: %s", v.name, err.Error()) - } - } -} - -func TestDeleteVolume(t *testing.T) { - vm := getvm() - // testcase - testcases := []struct { - name string - id string - isError bool - }{ - { - name: "delete first volume", - id: volumeId1, - isError: false, - }, - { - name: "delete first volume repeatedly", - id: volumeId1, - isError: true, - }, - { - name: "delete not exist volume", - id: "vol-1234567", - isError: true, - }, - } - for _, v := range testcases { - err := vm.DeleteVolume(v.id) - if err != nil && !v.isError { - t.Errorf("error name %s: %s", v.name, err.Error()) - } - } -} - -func TestResizeVolume(t *testing.T) { - vm := getvm() - // testcase - testcases := []struct { - name string - id string - size int - isError bool - }{ - { - name: "resize normally", - id: resizeVolumeId, - size: 30, - isError: false, - }, - } - for _, v := range testcases { - err := vm.ResizeVolume(v.id, v.size) - if err != nil && !v.isError { - t.Errorf("name %s: expect [%t] but actually [%s]", v.name, v.isError, err) - } - } -} - -func TestCreateVolumeFromSnapshot(t *testing.T) { - vm := getvm() - testcases := []struct { - name string - volumeName string - snapshotId string - isError bool - }{ - { - name: "create normally", - volumeName: volFromSnapNorm, - snapshotId: snapshotId1, - isError: false, - }, - { - name: "zero value input", - volumeName: volFromSnapNorm, - snapshotId: "", - isError: true, - }, - } - for _, v := range testcases { - _, err := vm.CreateVolumeFromSnapshot(v.volumeName, v.snapshotId) - if (err != nil) != v.isError { - t.Errorf("name %s: expect %t, but actually %s", v.name, v.isError, err) - } - } -} diff --git a/pkg/server/zone/zone_manager.go b/pkg/server/zone/zone_manager.go deleted file mode 100644 index 914336b2..00000000 --- a/pkg/server/zone/zone_manager.go +++ /dev/null @@ -1,69 +0,0 @@ -package zone - -import ( - "github.com/golang/glog" - "github.com/yunify/qingcloud-csi/pkg/server" - qcconfig "github.com/yunify/qingcloud-sdk-go/config" - qcservice "github.com/yunify/qingcloud-sdk-go/service" -) - -const ( - ZoneStatusActive = "active" - ZoneStatusFaulty = "faulty" - ZoneStatusDefunct = "defunct" -) - -type ZoneManager interface { - GetZoneList() ([]string, error) -} - -type zoneManager struct { - zoneService *qcservice.QingCloudService -} - -// NewZoneManagerFromConfig -// Create zone manager from config -func NewZoneManagerFromConfig(config *qcconfig.Config) (ZoneManager, error) { - // initial qingcloud iaas service - qs, err := qcservice.Init(config) - if err != nil { - return nil, err - } - // initial zone provisioner - zm := zoneManager{ - zoneService: qs, - } - glog.Infof("Finished initial zone manager") - return &zm, nil -} - -// NewZoneManagerFromFile -// Create zone manager from file -func NewZoneManagerFromFile(filePath string) (ZoneManager, error) { - config, err := server.ReadConfigFromFile(filePath) - if err != nil { - glog.Errorf("Failed read config file [%s], error: [%s]", filePath, err.Error()) - return nil, err - } - glog.Infof("Succeed read config file [%s]", filePath) - return NewZoneManagerFromConfig(config) -} - -// GetZoneList gets active zone list -func (zm *zoneManager) GetZoneList() (zones []string, err error) { - output, err := zm.zoneService.DescribeZones(&qcservice.DescribeZonesInput{}) - // Error: - // 1. Error is not equal to nil. - if err != nil { - return nil, err - } - if output == nil { - glog.Errorf("should not response [%#v]", output) - } - for i := range output.ZoneSet { - if *output.ZoneSet[i].Status == ZoneStatusActive { - zones = append(zones, *output.ZoneSet[i].ZoneID) - } - } - return zones, nil -} diff --git a/pkg/server/zone/zone_manager_test.go b/pkg/server/zone/zone_manager_test.go deleted file mode 100644 index c2477b2b..00000000 --- a/pkg/server/zone/zone_manager_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package zone - -import ( - "testing" -) - -var getzm = func() ZoneManager { - // get storage class - filePath := "/root/.qingcloud/config.yaml" - vm, err := NewZoneManagerFromFile(filePath) - if err != nil { - return nil - } - return vm -} - -func TestGetZoneList(t *testing.T) { - zm := getzm() - // testcase - testcases := []struct { - name string - }{ - { - name: "Get zone list", - }, - } - - // test findVolume - for _, v := range testcases { - zones, _ := zm.GetZoneList() - if len(zones) <= 0 { - t.Errorf("name %s: expected get at least one active zone, but actually [%d] active zones", v.name, - len(zones)) - } - } -} diff --git a/vendor/github.com/kubernetes-csi/drivers/LICENSE b/vendor/github.com/kubernetes-csi/drivers/LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/vendor/github.com/kubernetes-csi/drivers/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/controllerserver-default.go b/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/controllerserver-default.go deleted file mode 100644 index db72dc2e..00000000 --- a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/controllerserver-default.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2017 The Kubernetes 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 csicommon - -import ( - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/golang/glog" - "golang.org/x/net/context" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -type DefaultControllerServer struct { - Driver *CSIDriver -} - -func (cs *DefaultControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (cs *DefaultControllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (cs *DefaultControllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (cs *DefaultControllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (cs *DefaultControllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (cs *DefaultControllerServer) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (cs *DefaultControllerServer) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -// ControllerGetCapabilities implements the default GRPC callout. -// Default supports all capabilities -func (cs *DefaultControllerServer) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { - glog.V(5).Infof("Using default ControllerGetCapabilities") - - return &csi.ControllerGetCapabilitiesResponse{ - Capabilities: cs.Driver.cap, - }, nil -} - -func (cs *DefaultControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (cs *DefaultControllerServer) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (cs *DefaultControllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} diff --git a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/driver.go b/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/driver.go deleted file mode 100644 index d20c6c80..00000000 --- a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/driver.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 2017 The Kubernetes 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 csicommon - -import ( - "fmt" - - "github.com/golang/glog" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/container-storage-interface/spec/lib/go/csi" -) - -type CSIDriver struct { - name string - nodeID string - version string - cap []*csi.ControllerServiceCapability - vc []*csi.VolumeCapability_AccessMode -} - -// Creates a NewCSIDriver object. Assumes vendor version is equal to driver version & -// does not support optional driver plugin info manifest field. Refer to CSI spec for more details. -func NewCSIDriver(name string, v string, nodeID string) *CSIDriver { - if name == "" { - glog.Errorf("Driver name missing") - return nil - } - - if nodeID == "" { - glog.Errorf("NodeID missing") - return nil - } - // TODO version format and validation - if len(v) == 0 { - glog.Errorf("Version argument missing") - return nil - } - - driver := CSIDriver{ - name: name, - version: v, - nodeID: nodeID, - } - - return &driver -} - -func (d *CSIDriver) ValidateControllerServiceRequest(c csi.ControllerServiceCapability_RPC_Type) error { - if c == csi.ControllerServiceCapability_RPC_UNKNOWN { - return nil - } - - for _, cap := range d.cap { - if c == cap.GetRpc().GetType() { - return nil - } - } - return status.Error(codes.InvalidArgument, fmt.Sprintf("%s", c)) -} - -func (d *CSIDriver) AddControllerServiceCapabilities(cl []csi.ControllerServiceCapability_RPC_Type) { - var csc []*csi.ControllerServiceCapability - - for _, c := range cl { - glog.Infof("Enabling controller service capability: %v", c.String()) - csc = append(csc, NewControllerServiceCapability(c)) - } - - d.cap = csc - - return -} - -func (d *CSIDriver) AddVolumeCapabilityAccessModes(vc []csi.VolumeCapability_AccessMode_Mode) []*csi.VolumeCapability_AccessMode { - var vca []*csi.VolumeCapability_AccessMode - for _, c := range vc { - glog.Infof("Enabling volume access mode: %v", c.String()) - vca = append(vca, NewVolumeCapabilityAccessMode(c)) - } - d.vc = vca - return vca -} - -func (d *CSIDriver) GetVolumeCapabilityAccessModes() []*csi.VolumeCapability_AccessMode { - return d.vc -} diff --git a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/identityserver-default.go b/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/identityserver-default.go deleted file mode 100644 index b16b67c0..00000000 --- a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/identityserver-default.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2017 The Kubernetes 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 csicommon - -import ( - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/golang/glog" - "golang.org/x/net/context" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -type DefaultIdentityServer struct { - Driver *CSIDriver -} - -func (ids *DefaultIdentityServer) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { - glog.V(5).Infof("Using default GetPluginInfo") - - if ids.Driver.name == "" { - return nil, status.Error(codes.Unavailable, "Driver name not configured") - } - - if ids.Driver.version == "" { - return nil, status.Error(codes.Unavailable, "Driver is missing version") - } - - return &csi.GetPluginInfoResponse{ - Name: ids.Driver.name, - VendorVersion: ids.Driver.version, - }, nil -} - -func (ids *DefaultIdentityServer) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) { - return &csi.ProbeResponse{}, nil -} - -func (ids *DefaultIdentityServer) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) { - glog.V(5).Infof("Using default capabilities") - return &csi.GetPluginCapabilitiesResponse{ - Capabilities: []*csi.PluginCapability{ - { - Type: &csi.PluginCapability_Service_{ - Service: &csi.PluginCapability_Service{ - Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, - }, - }, - }, - }, - }, nil -} diff --git a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/nodeserver-default.go b/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/nodeserver-default.go deleted file mode 100644 index cd2355bf..00000000 --- a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/nodeserver-default.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2017 The Kubernetes 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 csicommon - -import ( - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/golang/glog" - "golang.org/x/net/context" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -type DefaultNodeServer struct { - Driver *CSIDriver -} - -func (ns *DefaultNodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (ns *DefaultNodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} - -func (ns *DefaultNodeServer) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { - glog.V(5).Infof("Using default NodeGetInfo") - - return &csi.NodeGetInfoResponse{ - NodeId: ns.Driver.nodeID, - }, nil -} - -func (ns *DefaultNodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { - glog.V(5).Infof("Using default NodeGetCapabilities") - - return &csi.NodeGetCapabilitiesResponse{ - Capabilities: []*csi.NodeServiceCapability{ - { - Type: &csi.NodeServiceCapability_Rpc{ - Rpc: &csi.NodeServiceCapability_RPC{ - Type: csi.NodeServiceCapability_RPC_UNKNOWN, - }, - }, - }, - }, - }, nil -} - -func (ns *DefaultNodeServer) NodeGetVolumeStats(ctx context.Context, in *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) { - return nil, status.Error(codes.Unimplemented, "") -} diff --git a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/utils.go b/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/utils.go deleted file mode 100644 index b39132e0..00000000 --- a/vendor/github.com/kubernetes-csi/drivers/pkg/csi-common/utils.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -Copyright 2017 The Kubernetes 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 csicommon - -import ( - "fmt" - "strings" - - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/golang/glog" - "github.com/kubernetes-csi/csi-lib-utils/protosanitizer" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -func ParseEndpoint(ep string) (string, string, error) { - if strings.HasPrefix(strings.ToLower(ep), "unix://") || strings.HasPrefix(strings.ToLower(ep), "tcp://") { - s := strings.SplitN(ep, "://", 2) - if s[1] != "" { - return s[0], s[1], nil - } - } - return "", "", fmt.Errorf("Invalid endpoint: %v", ep) -} - -func NewVolumeCapabilityAccessMode(mode csi.VolumeCapability_AccessMode_Mode) *csi.VolumeCapability_AccessMode { - return &csi.VolumeCapability_AccessMode{Mode: mode} -} - -func NewDefaultNodeServer(d *CSIDriver) *DefaultNodeServer { - return &DefaultNodeServer{ - Driver: d, - } -} - -func NewDefaultIdentityServer(d *CSIDriver) *DefaultIdentityServer { - return &DefaultIdentityServer{ - Driver: d, - } -} - -func NewDefaultControllerServer(d *CSIDriver) *DefaultControllerServer { - return &DefaultControllerServer{ - Driver: d, - } -} - -func NewControllerServiceCapability(cap csi.ControllerServiceCapability_RPC_Type) *csi.ControllerServiceCapability { - return &csi.ControllerServiceCapability{ - Type: &csi.ControllerServiceCapability_Rpc{ - Rpc: &csi.ControllerServiceCapability_RPC{ - Type: cap, - }, - }, - } -} - -func RunNodePublishServer(endpoint string, d *CSIDriver, ns csi.NodeServer) { - ids := NewDefaultIdentityServer(d) - - s := NewNonBlockingGRPCServer() - s.Start(endpoint, ids, nil, ns) - s.Wait() -} - -func RunControllerPublishServer(endpoint string, d *CSIDriver, cs csi.ControllerServer) { - ids := NewDefaultIdentityServer(d) - - s := NewNonBlockingGRPCServer() - s.Start(endpoint, ids, cs, nil) - s.Wait() -} - -func RunControllerandNodePublishServer(endpoint string, d *CSIDriver, cs csi.ControllerServer, ns csi.NodeServer) { - ids := NewDefaultIdentityServer(d) - - s := NewNonBlockingGRPCServer() - s.Start(endpoint, ids, cs, ns) - s.Wait() -} - -func logGRPC(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - glog.V(3).Infof("GRPC call: %s", info.FullMethod) - glog.V(5).Infof("GRPC request: %s", protosanitizer.StripSecrets(req)) - resp, err := handler(ctx, req) - if err != nil { - glog.Errorf("GRPC error: %v", err) - } else { - glog.V(5).Infof("GRPC response: %s", protosanitizer.StripSecrets(resp)) - } - return resp, err -}