From d2d068abee016f205c0cc5707c5943e9edaa7650 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Mon, 1 Jul 2019 20:11:10 +0800 Subject: [PATCH] add create and delete snapshot --- Makefile | 2 + .../disk/example/snapshot/deploy-viewer.yaml | 44 +++ .../disk/example/snapshot/deploy-writer.yaml | 46 ++++ .../disk/example/snapshot/docker/Dockerfile | 23 ++ deploy/disk/example/snapshot/docker/Makefile | 27 ++ .../example/snapshot/docker/start-test.sh | 2 + deploy/disk/example/snapshot/docker/test.fio | 32 +++ deploy/disk/example/snapshot/pvc.yaml | 27 ++ deploy/disk/example/snapshot/restore-pvc.yaml | 31 +++ deploy/disk/example/snapshot/secret.yaml | 21 ++ .../disk/example/snapshot/snapshot-class.yaml | 21 ++ .../example/snapshot/volume-snapshot.yaml | 25 ++ deploy/disk/example/{ => volume}/deploy.yaml | 0 deploy/disk/example/{ => volume}/pvc.yaml | 2 +- deploy/disk/example/{ => volume}/sc.yaml | 0 deploy/disk/kubernetes/base/config.yaml | 2 +- .../kubernetes/base/controller-deploy.yaml | 15 +- .../disk/kubernetes/base/controller-rbac.yaml | 66 +++++ pkg/disk/controllerserver.go | 194 +++++++++++-- pkg/disk/disk.go | 1 + pkg/server/instance/instance_manager_test.go | 11 +- pkg/server/snapshot/snapshot_manager.go | 255 ++++++++++++++++++ pkg/server/snapshot/snapshot_manager_test.go | 187 +++++++++++++ pkg/server/volume/volume_manager.go | 2 +- pkg/server/volume/volume_manager_test.go | 13 +- pkg/server/zone/zone_manager_test.go | 11 +- 26 files changed, 1009 insertions(+), 51 deletions(-) create mode 100644 deploy/disk/example/snapshot/deploy-viewer.yaml create mode 100644 deploy/disk/example/snapshot/deploy-writer.yaml create mode 100644 deploy/disk/example/snapshot/docker/Dockerfile create mode 100644 deploy/disk/example/snapshot/docker/Makefile create mode 100644 deploy/disk/example/snapshot/docker/start-test.sh create mode 100644 deploy/disk/example/snapshot/docker/test.fio create mode 100644 deploy/disk/example/snapshot/pvc.yaml create mode 100644 deploy/disk/example/snapshot/restore-pvc.yaml create mode 100644 deploy/disk/example/snapshot/secret.yaml create mode 100644 deploy/disk/example/snapshot/snapshot-class.yaml create mode 100644 deploy/disk/example/snapshot/volume-snapshot.yaml rename deploy/disk/example/{ => volume}/deploy.yaml (100%) rename deploy/disk/example/{ => volume}/pvc.yaml (97%) rename deploy/disk/example/{ => volume}/sc.yaml (100%) create mode 100644 pkg/server/snapshot/snapshot_manager.go create mode 100644 pkg/server/snapshot/snapshot_manager_test.go diff --git a/Makefile b/Makefile index 86ae8981..7f36a35c 100644 --- a/Makefile +++ b/Makefile @@ -31,12 +31,14 @@ disk-container: disk docker build -t $(DISK_IMAGE_NAME):$(DISK_IMAGE_VERSION) deploy/disk/docker install-dev: + cp /root/.qingcloud/config.yaml deploy/disk/kubernetes/base/config.yaml kustomize build deploy/disk/kubernetes/overlays/dev|kubectl apply -f - uninstall-dev: kustomize build deploy/disk/kubernetes/overlays/dev|kubectl delete -f - gen-dev: + cp /root/.qingcloud/config.yaml deploy/disk/kubernetes/base/config.yaml kustomize build deploy/disk/kubernetes/overlays/dev install-prod: diff --git a/deploy/disk/example/snapshot/deploy-viewer.yaml b/deploy/disk/example/snapshot/deploy-viewer.yaml new file mode 100644 index 00000000..ae397ab4 --- /dev/null +++ b/deploy/disk/example/snapshot/deploy-viewer.yaml @@ -0,0 +1,44 @@ +# +------------------------------------------------------------------------- +# | 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. +# +------------------------------------------------------------------------- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + selector: + matchLabels: + app: nginx + tier: csi-qingcloud + replicas: 1 + template: + metadata: + labels: + app: nginx + tier: csi-qingcloud + spec: + containers: + - name: nginx + image: nginx + volumeMounts: + - mountPath: /mnt + name: mypvc + volumes: + - name: mypvc + persistentVolumeClaim: + claimName: restore-from-snap + readOnly: false + diff --git a/deploy/disk/example/snapshot/deploy-writer.yaml b/deploy/disk/example/snapshot/deploy-writer.yaml new file mode 100644 index 00000000..cfb373e9 --- /dev/null +++ b/deploy/disk/example/snapshot/deploy-writer.yaml @@ -0,0 +1,46 @@ +# +------------------------------------------------------------------------- +# | 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. +# +------------------------------------------------------------------------- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: fio +spec: + selector: + matchLabels: + app: fio + tier: csi-qingcloud + replicas: 1 + template: + metadata: + labels: + app: fio + tier: csi-qingcloud + spec: + imagePullSecrets: + - name: csi-qingcloud + containers: + - name: fio + image: dockerhub.qingcloud.com/csiplugin/fio-test:latest + volumeMounts: + - mountPath: /mnt + name: mypvc + volumes: + - name: mypvc + persistentVolumeClaim: + claimName: pvc-for-snap + readOnly: false + diff --git a/deploy/disk/example/snapshot/docker/Dockerfile b/deploy/disk/example/snapshot/docker/Dockerfile new file mode 100644 index 00000000..bd422603 --- /dev/null +++ b/deploy/disk/example/snapshot/docker/Dockerfile @@ -0,0 +1,23 @@ +# +------------------------------------------------------------------------- +# | 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. +# +------------------------------------------------------------------------- + + +FROM nginx +LABEL maintainers="Yunify" +LABEL description="QingCloud CSI plugin" +RUN apt update && apt install vim fio -y +COPY test.fio start-test.sh /root/ +RUN chmod +x /root/start-test.sh \ No newline at end of file diff --git a/deploy/disk/example/snapshot/docker/Makefile b/deploy/disk/example/snapshot/docker/Makefile new file mode 100644 index 00000000..2109ff34 --- /dev/null +++ b/deploy/disk/example/snapshot/docker/Makefile @@ -0,0 +1,27 @@ +# +------------------------------------------------------------------------- +# | 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. +# +------------------------------------------------------------------------- + +.PHONY: all image + +IMAGE_REPO=dockerhub.qingcloud.com/csiplugin/ +IMAGE_NAME=fio-test +IMAGE_TAG=latest + +image: + docker build -t $(IMAGE_REPO)$(IMAGE_NAME):$(IMAGE_TAG) ./ + +clean: + docker rmi $(IMAGE_REPO)$(IMAGE_NAME):$(IMAGE_TAG) \ No newline at end of file diff --git a/deploy/disk/example/snapshot/docker/start-test.sh b/deploy/disk/example/snapshot/docker/start-test.sh new file mode 100644 index 00000000..0814a1ad --- /dev/null +++ b/deploy/disk/example/snapshot/docker/start-test.sh @@ -0,0 +1,2 @@ +# !/bin/sh +fio /root/test.fio -directory /mnt -output /root/test.result \ No newline at end of file diff --git a/deploy/disk/example/snapshot/docker/test.fio b/deploy/disk/example/snapshot/docker/test.fio new file mode 100644 index 00000000..2e6e7144 --- /dev/null +++ b/deploy/disk/example/snapshot/docker/test.fio @@ -0,0 +1,32 @@ +[global] +bs=4k +ioengine=libaio +iodepth=16 +size=10g +direct=1 +runtime=60 +numjobs=4 + +[seq-read] +rw=read +stonewall + +[rand-read] +rw=randread +stonewall + +[seq-write] +rw=write +stonewall + +[rand-write] +rw=randwrite +stonewall + +[seq-rw] +rw=rw +stonewall + +[rand-rw] +rw=randrw +stonewall \ No newline at end of file diff --git a/deploy/disk/example/snapshot/pvc.yaml b/deploy/disk/example/snapshot/pvc.yaml new file mode 100644 index 00000000..5735cdd8 --- /dev/null +++ b/deploy/disk/example/snapshot/pvc.yaml @@ -0,0 +1,27 @@ +# +------------------------------------------------------------------------- +# | 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. +# +------------------------------------------------------------------------- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pvc-for-snap +spec: + storageClassName: csi-qingcloud + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi \ No newline at end of file diff --git a/deploy/disk/example/snapshot/restore-pvc.yaml b/deploy/disk/example/snapshot/restore-pvc.yaml new file mode 100644 index 00000000..f435a095 --- /dev/null +++ b/deploy/disk/example/snapshot/restore-pvc.yaml @@ -0,0 +1,31 @@ +# +------------------------------------------------------------------------- +# | 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. +# +------------------------------------------------------------------------- + +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: restore-from-snap +spec: + storageClassName: csi-qingcloud + dataSource: + name: pvc-for-snap + kind: VolumeSnapshot + apiGroup: snapshot.storage.k8s.io + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi \ No newline at end of file diff --git a/deploy/disk/example/snapshot/secret.yaml b/deploy/disk/example/snapshot/secret.yaml new file mode 100644 index 00000000..8c072878 --- /dev/null +++ b/deploy/disk/example/snapshot/secret.yaml @@ -0,0 +1,21 @@ +# 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. + +apiVersion: v1 +kind: Secret +metadata: + name: csi-qingcloud +data: + .dockerconfigjson: eyJhdXRocyI6eyJkb2NrZXJodWIucWluZ2Nsb3VkLmNvbSI6eyJhdXRoIjoiWjNWbGMzUTZaM1ZsYzNRPSJ9fX0= +type: kubernetes.io/dockerconfigjson \ No newline at end of file diff --git a/deploy/disk/example/snapshot/snapshot-class.yaml b/deploy/disk/example/snapshot/snapshot-class.yaml new file mode 100644 index 00000000..584db52c --- /dev/null +++ b/deploy/disk/example/snapshot/snapshot-class.yaml @@ -0,0 +1,21 @@ +# +------------------------------------------------------------------------- +# | 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. +# +------------------------------------------------------------------------- + +apiVersion: snapshot.storage.k8s.io/v1alpha1 +kind: VolumeSnapshotClass +metadata: + name: csi-qingcloud +snapshotter: disk.csi.qingcloud.com \ No newline at end of file diff --git a/deploy/disk/example/snapshot/volume-snapshot.yaml b/deploy/disk/example/snapshot/volume-snapshot.yaml new file mode 100644 index 00000000..c008fa5f --- /dev/null +++ b/deploy/disk/example/snapshot/volume-snapshot.yaml @@ -0,0 +1,25 @@ +# +------------------------------------------------------------------------- +# | 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. +# +------------------------------------------------------------------------- + +apiVersion: snapshot.storage.k8s.io/v1alpha1 +kind: VolumeSnapshot +metadata: + name: snap-test +spec: + snapshotClassName: csi-qingcloud + source: + name: pvc-for-snap + kind: PersistentVolumeClaim \ No newline at end of file diff --git a/deploy/disk/example/deploy.yaml b/deploy/disk/example/volume/deploy.yaml similarity index 100% rename from deploy/disk/example/deploy.yaml rename to deploy/disk/example/volume/deploy.yaml diff --git a/deploy/disk/example/pvc.yaml b/deploy/disk/example/volume/pvc.yaml similarity index 97% rename from deploy/disk/example/pvc.yaml rename to deploy/disk/example/volume/pvc.yaml index a36d8e23..916fc15f 100644 --- a/deploy/disk/example/pvc.yaml +++ b/deploy/disk/example/volume/pvc.yaml @@ -24,5 +24,5 @@ spec: - ReadWriteOnce resources: requests: - storage: 345Gi + storage: 20Gi storageClassName: csi-qingcloud diff --git a/deploy/disk/example/sc.yaml b/deploy/disk/example/volume/sc.yaml similarity index 100% rename from deploy/disk/example/sc.yaml rename to deploy/disk/example/volume/sc.yaml diff --git a/deploy/disk/kubernetes/base/config.yaml b/deploy/disk/kubernetes/base/config.yaml index 616b0487..e5b0d481 100644 --- a/deploy/disk/kubernetes/base/config.yaml +++ b/deploy/disk/kubernetes/base/config.yaml @@ -20,4 +20,4 @@ port: 443 protocol: 'https' uri: '/iaas' connection_retries: 3 -connection_timeout: 30 +connection_timeout: 30 \ No newline at end of file diff --git a/deploy/disk/kubernetes/base/controller-deploy.yaml b/deploy/disk/kubernetes/base/controller-deploy.yaml index 37fb5f1d..2b417885 100644 --- a/deploy/disk/kubernetes/base/controller-deploy.yaml +++ b/deploy/disk/kubernetes/base/controller-deploy.yaml @@ -55,7 +55,7 @@ spec: - "--csi-address=$(ADDRESS)" - "--enable-leader-election" - "--leader-election-type=leases" - - "--timeout=60s" + - "--timeout=90s" - "--retry-interval-start=5s" - "--retry-interval-max=5m" - "--worker-threads=5" @@ -85,6 +85,19 @@ spec: volumeMounts: - name: socket-dir mountPath: /csi + - name: csi-snapshotter + image: quay.io/k8scsi/csi-snapshotter:v1.2.0 + args: + - "--csi-address=$(ADDRESS)" + - "--connection-timeout=90s" + - "--leader-election=false" + env: + - name: ADDRESS + value: /csi/csi.sock + imagePullPolicy: "IfNotPresent" + volumeMounts: + - name: socket-dir + mountPath: /csi - name: csi-resizer image: quay.io/k8scsi/csi-resizer:v0.1.0 args: diff --git a/deploy/disk/kubernetes/base/controller-rbac.yaml b/deploy/disk/kubernetes/base/controller-rbac.yaml index bd3cb56e..c6216c08 100644 --- a/deploy/disk/kubernetes/base/controller-rbac.yaml +++ b/deploy/disk/kubernetes/base/controller-rbac.yaml @@ -77,6 +77,44 @@ rules: resources: ["volumeattachments"] verbs: ["get", "list", "watch", "update"] --- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: csi-qingcloud-snapshotter + labels: + role: "controller" +rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["list", "watch", "create", "update", "patch"] + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list"] + - apiGroups: ["snapshot.storage.k8s.io"] + resources: ["volumesnapshotclasses"] + verbs: ["get", "list", "watch"] + - apiGroups: ["snapshot.storage.k8s.io"] + resources: ["volumesnapshotcontents"] + verbs: ["create", "get", "list", "watch", "update", "delete"] + - apiGroups: ["snapshot.storage.k8s.io"] + resources: ["volumesnapshots"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["snapshot.storage.k8s.io"] + resources: ["volumesnapshots/status"] + verbs: ["update"] + - apiGroups: ["apiextensions.k8s.io"] + resources: ["customresourcedefinitions"] + verbs: ["create", "list", "watch", "delete", "get", "update"] +--- # Resizer must be able to work with PVCs, PVs, SCs. kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 @@ -224,6 +262,34 @@ roleRef: name: csi-qingcloud-attacher apiGroup: rbac.authorization.k8s.io --- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: csi-qingcloud-snapshotter + namespace: kube-system + labels: + role: "controller" +rules: + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "watch", "list", "delete", "update", "create"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: csi-qingcloud-snapshotter + namespace: kube-system + labels: + role: "controller" +subjects: + - kind: ServiceAccount + name: csi-snapshotter + namespace: kube-system +roleRef: + kind: Role + name: csi-qingcloud-snapshotter + apiGroup: rbac.authorization.k8s.io +--- # Resizer must be able to work with end point in current namespace # if (and only if) leadership election is enabled kind: Role diff --git a/pkg/disk/controllerserver.go b/pkg/disk/controllerserver.go index 8d47605a..64726559 100644 --- a/pkg/disk/controllerserver.go +++ b/pkg/disk/controllerserver.go @@ -20,9 +20,12 @@ import ( "fmt" "github.com/container-storage-interface/spec/lib/go/csi" "github.com/golang/glog" + "github.com/golang/protobuf/ptypes" + timestamp2 "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" "golang.org/x/net/context" @@ -173,7 +176,7 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol 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) { - time.Sleep(time.Duration(i) * time.Second) + time.Sleep(time.Duration(i*2) * time.Second) } else { return nil, status.Error(codes.Internal, err.Error()) } @@ -255,22 +258,37 @@ func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *cs if err != nil { return nil, status.Error(codes.Internal, err.Error()) } - volInfo, err := vm.FindVolume(volumeId) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - // check device path - if *volInfo.Instance.Device == "" { - glog.Infof("Cannot find device path and going to detach volume %s", volumeId) - err := vm.DetachVolume(volumeId, nodeId) + + // When return with retry message at describe volume, retry after several seconds. + // 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) if err != nil { - return nil, status.Errorf(codes.Internal, "try to detach volume %s failed", volumeId) + return nil, status.Error(codes.Internal, err.Error()) } + // check device path + if *volInfo.Instance.Device != "" { + // found device path + glog.Infof("Attaching volume %s on instance %s succeed.", volumeId, nodeId) + return &csi.ControllerPublishVolumeResponse{}, nil + } else { + // cannot found device path + glog.Infof("Cannot find device path and retry to find volume device %s", volumeId) + time.Sleep(time.Duration(i) * time.Second) + } + } + // 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 { return nil, status.Errorf(codes.Internal, - "cannot find device path, please re-attach volume %s to instance %s", volumeId, nodeId) + "cannot find device path, detach volume %s failed", volumeId) + } else { + return nil, status.Errorf(codes.Internal, + "cannot find device path, volume %s has been detached, please try attaching to instance %s again.", + volumeId, nodeId) } - glog.Infof("Attaching volume %s on instance %s succeed.", volumeId, nodeId) - return &csi.ControllerPublishVolumeResponse{}, nil } // This operation MUST be idempotent @@ -457,12 +475,158 @@ func (cs *controllerServer) GetCapacity(ctx context.Context, req *csi.GetCapacit return nil, status.Error(codes.Unimplemented, "") } +// CreateSnapshot allows the CO to create a snapshot. +// This operation MUST be idempotent. +// 1. If snapshot successfully cut and ready to use, the plugin MUST reply 0 OK. +// 2. If an error occurs before a snapshot is cut, the plugin SHOULD reply a corresponding error code. +// 3. If snapshot successfully cut but still being precessed, +// 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) { - return nil, status.Error(codes.Unimplemented, "") + glog.Info("----- Start CreateSnapshot -----") + defer glog.Info("===== End CreateSnapshot =====") + if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); err != nil { + glog.Errorf("invalid create snapshot request: %v", req) + return nil, err + } + // 0. Preflight + // Check source volume id + if len(req.GetSourceVolumeId()) == 0 { + return nil, status.Error(codes.InvalidArgument, "volume ID missing in request") + } + // Check snapshot name + if len(req.GetName()) == 0 { + return nil, status.Error(codes.InvalidArgument, "snapshot name missing in request") + } + + // Create snapshot manager object + sm, err := snapshot.NewSnapshotManagerFromFile(cs.cloudServer.GetConfigFilePath()) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + sourceVolumeId := req.GetSourceVolumeId() + snapshotName := req.GetName() + var timestamp *timestamp2.Timestamp + var isReadyToUse bool + // For idempotent + // If a snapshot corresponding to the specified snapshot name is successfully cut and ready to use (meaning it MAY + // be specified as a volume_content_source in a CreateVolumeRequest), the Plugin MUST reply 0 OK with the + // corresponding CreateSnapshotResponse. + exSnap, err := sm.FindSnapshotByName(snapshotName) + if err != nil { + return nil, status.Errorf(codes.Internal, "find snapshot by name error: %s, %s", snapshotName, err.Error()) + } + if exSnap != nil { + glog.Infof("Exist snapshot name: %s, snapshot id %s, source volume id %s", + *exSnap.SnapshotName, *exSnap.SnapshotID, *exSnap.Resource.ResourceID) + if exSnap.Resource != nil && *exSnap.Resource.ResourceType == "volume" && + *exSnap.Resource.ResourceID == sourceVolumeId { + timestamp, err = ptypes.TimestampProto(*exSnap.CreateTime) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + if *exSnap.Status == snapshot.SnapshotStatusAvailable { + isReadyToUse = true + } else { + isReadyToUse = false + } + return &csi.CreateSnapshotResponse{ + Snapshot: &csi.Snapshot{ + SizeBytes: int64(*exSnap.Size) * server.Mib, + SnapshotId: *exSnap.SnapshotID, + SourceVolumeId: *exSnap.Resource.ResourceID, + CreationTime: timestamp, + ReadyToUse: isReadyToUse, + }, + }, nil + } + return nil, status.Errorf(codes.AlreadyExists, + "snapshot name=[%s] id=[%s] already exists, but is incompatible with the volume id=[%s]", + snapshotName, *exSnap.SnapshotID, sourceVolumeId) + } + // Create a new full snapshot + glog.Infof("Creating snapshot %s from volume %s in zone %s...", snapshotName, sourceVolumeId, sm.GetZone()) + snapId, err := sm.CreateSnapshot(snapshotName, sourceVolumeId) + if err != nil { + return nil, status.Errorf(codes.Internal, "create snapshot [%s] from source volume [%s] error: %s", + snapshotName, sourceVolumeId, err.Error()) + } + snapInfo, err := sm.FindSnapshot(snapId) + if err != nil { + return nil, status.Errorf(codes.Internal, "find snapshot [%s] error: %s", snapId, err.Error()) + } + timestamp, err = ptypes.TimestampProto(*snapInfo.CreateTime) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + if *snapInfo.Status == snapshot.SnapshotStatusAvailable { + isReadyToUse = true + } else { + isReadyToUse = false + } + return &csi.CreateSnapshotResponse{ + Snapshot: &csi.Snapshot{ + SizeBytes: int64(*snapInfo.Size) * server.Mib, + SnapshotId: *snapInfo.SnapshotID, + SourceVolumeId: *snapInfo.Resource.ResourceID, + CreationTime: timestamp, + ReadyToUse: isReadyToUse, + }, + }, nil } +// 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) { - return nil, status.Error(codes.Unimplemented, "") + glog.Info("----- Start DeleteSnapshot -----") + defer glog.Info("===== End DeleteSnapshot =====") + if err := cs.Driver.ValidateControllerServiceRequest(csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); err != nil { + glog.Errorf("invalid create snapshot request: %v", req) + return nil, err + } + // 0. Preflight + // Check snapshot id + if len(req.GetSnapshotId()) == 0 { + return nil, status.Error(codes.InvalidArgument, "snapshot ID missing in request") + } + snapshotId := req.GetSnapshotId() + + // Create snapshot manager object + 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 + exSnap, err := sm.FindSnapshot(snapshotId) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + if exSnap == nil { + return &csi.DeleteSnapshotResponse{}, nil + } + // 2. Delete snapshot + glog.Infof("Deleting snapshot %s in zone %s...", snapshotId, sm.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++ { + err = sm.DeleteSnapshot(snapshotId) + if err != nil { + glog.Infof("Failed to delete snapshot %s in %s with error: %v", snapshotId, sm.GetZone(), err) + if strings.Contains(err.Error(), server.RetryString) { + time.Sleep(time.Duration(i*2) * time.Second) + } else { + return nil, status.Error(codes.Internal, err.Error()) + } + } else { + return &csi.DeleteSnapshotResponse{}, nil + } + } + return nil, status.Error(codes.Internal, "Exceed retry times: "+err.Error()) } func (cs *controllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { diff --git a/pkg/disk/disk.go b/pkg/disk/disk.go index 963b60ba..69782acd 100644 --- a/pkg/disk/disk.go +++ b/pkg/disk/disk.go @@ -85,6 +85,7 @@ func (d *disk) Run(driverName, nodeID, endpoint string, serverConfig *server.Ser 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}) diff --git a/pkg/server/instance/instance_manager_test.go b/pkg/server/instance/instance_manager_test.go index 38746557..ec135368 100644 --- a/pkg/server/instance/instance_manager_test.go +++ b/pkg/server/instance/instance_manager_test.go @@ -17,21 +17,12 @@ package instance import ( - "os" - "path" - "runtime" "testing" ) var getim = func() InstanceManager { // get storage class - var filePath string - if runtime.GOOS == "linux" { - filePath = path.Join(os.Getenv("GOPATH"), "src/github.com/yunify/qingcloud-csi/deploy/disk/kubernetes/config.yaml") - } - if runtime.GOOS == "darwin" { - filePath = path.Join(os.Getenv("GOPATH"), "src/github.com/yunify/qingcloud-csi/deploy/disk/kubernetes/config.yaml") - } + filePath := "/root/.qingcloud/config.yaml" im, err := NewInstanceManagerFromFile(filePath) if err != nil { return nil diff --git a/pkg/server/snapshot/snapshot_manager.go b/pkg/server/snapshot/snapshot_manager.go new file mode 100644 index 00000000..9cf70705 --- /dev/null +++ b/pkg/server/snapshot/snapshot_manager.go @@ -0,0 +1,255 @@ +// +------------------------------------------------------------------------- +// | 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 new file mode 100644 index 00000000..d7d79719 --- /dev/null +++ b/pkg/server/snapshot/snapshot_manager_test.go @@ -0,0 +1,187 @@ +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/volume/volume_manager.go b/pkg/server/volume/volume_manager.go index e6ee0d7a..e5b5a9c5 100644 --- a/pkg/server/volume/volume_manager.go +++ b/pkg/server/volume/volume_manager.go @@ -67,7 +67,7 @@ func NewVolumeManagerFromConfig(config *qcconfig.Config) (VolumeManager, error) vs, _ := qs.Volume(config.Zone) // create job service js, _ := qs.Job(config.Zone) - // initial volume provisioner + // initial volume manager vm := volumeManager{ volumeService: vs, jobService: js, diff --git a/pkg/server/volume/volume_manager_test.go b/pkg/server/volume/volume_manager_test.go index 095d130c..3dff970f 100644 --- a/pkg/server/volume/volume_manager_test.go +++ b/pkg/server/volume/volume_manager_test.go @@ -19,9 +19,6 @@ package volume import ( "github.com/yunify/qingcloud-csi/pkg/server" "github.com/yunify/qingcloud-csi/pkg/server/storageclass" - "os" - "path" - "runtime" "testing" ) @@ -36,15 +33,7 @@ var ( var getvm = func() VolumeManager { // get storage class - var filePath string - if runtime.GOOS == "linux" { - filePath = path.Join(os.Getenv("GOPATH"), - "src/github.com/yunify/qingcloud-csi/deploy/disk/kubernetes/config.yaml") - } - if runtime.GOOS == "darwin" { - filePath = path.Join(os.Getenv("GOPATH"), - "src/github.com/yunify/qingcloud-csi/deploy/disk/kubernetes/config.yaml") - } + filePath := "/root/.qingcloud/config.yaml" vm, err := NewVolumeManagerFromFile(filePath) if err != nil { return nil diff --git a/pkg/server/zone/zone_manager_test.go b/pkg/server/zone/zone_manager_test.go index 8ac282f3..c2477b2b 100644 --- a/pkg/server/zone/zone_manager_test.go +++ b/pkg/server/zone/zone_manager_test.go @@ -1,21 +1,12 @@ package zone import ( - "os" - "path" - "runtime" "testing" ) var getzm = func() ZoneManager { // get storage class - var filePath string - if runtime.GOOS == "linux" { - filePath = path.Join(os.Getenv("GOPATH"), "src/github.com/yunify/qingcloud-csi/deploy/disk/kubernetes/config.yaml") - } - if runtime.GOOS == "darwin" { - filePath = path.Join(os.Getenv("GOPATH"), "src/github.com/yunify/qingcloud-csi/deploy/disk/kubernetes/config.yaml") - } + filePath := "/root/.qingcloud/config.yaml" vm, err := NewZoneManagerFromFile(filePath) if err != nil { return nil