diff --git a/deploy/disk/example/snapshot/deploy-viewer.yaml b/deploy/disk/example/snapshot/deploy-viewer.yaml index 2c3701fc..1a9d0767 100644 --- a/deploy/disk/example/snapshot/deploy-viewer.yaml +++ b/deploy/disk/example/snapshot/deploy-viewer.yaml @@ -1,33 +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. -# +------------------------------------------------------------------------- +# 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 + name: snap-example spec: selector: matchLabels: - app: nginx + app: snap-example tier: csi-qingcloud replicas: 1 template: metadata: labels: - app: nginx + app: snap-example tier: csi-qingcloud spec: containers: diff --git a/deploy/disk/example/snapshot/deploy-writer.yaml b/deploy/disk/example/snapshot/deploy-writer.yaml index 0ab2c989..6f98288d 100644 --- a/deploy/disk/example/snapshot/deploy-writer.yaml +++ b/deploy/disk/example/snapshot/deploy-writer.yaml @@ -1,18 +1,16 @@ -# +------------------------------------------------------------------------- -# | 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. -# +------------------------------------------------------------------------- +# 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 diff --git a/deploy/disk/example/snapshot/original-pvc.yaml b/deploy/disk/example/snapshot/original-pvc.yaml new file mode 100644 index 00000000..9b7244a2 --- /dev/null +++ b/deploy/disk/example/snapshot/original-pvc.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: v1 +kind: PersistentVolumeClaim +metadata: + name: pvc-snap-1 +spec: + storageClassName: csi-qingcloud + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi \ No newline at end of file diff --git a/deploy/disk/example/snapshot/pvc.yaml b/deploy/disk/example/snapshot/pvc.yaml deleted file mode 100644 index 485af968..00000000 --- a/deploy/disk/example/snapshot/pvc.yaml +++ /dev/null @@ -1,27 +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. -# +------------------------------------------------------------------------- - -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: pvc-snap-1 -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 index 6725c985..7fa6eb40 100644 --- a/deploy/disk/example/snapshot/restore-pvc.yaml +++ b/deploy/disk/example/snapshot/restore-pvc.yaml @@ -1,18 +1,16 @@ -# +------------------------------------------------------------------------- -# | 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. -# +------------------------------------------------------------------------- +# 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 diff --git a/deploy/disk/example/snapshot/snapshot-class.yaml b/deploy/disk/example/snapshot/snapshot-class.yaml index 584db52c..40957e46 100644 --- a/deploy/disk/example/snapshot/snapshot-class.yaml +++ b/deploy/disk/example/snapshot/snapshot-class.yaml @@ -1,18 +1,16 @@ -# +------------------------------------------------------------------------- -# | 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. -# +------------------------------------------------------------------------- +# 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 diff --git a/deploy/disk/example/snapshot/volume-snapshot.yaml b/deploy/disk/example/snapshot/volume-snapshot.yaml index 228b5776..80e62d9a 100644 --- a/deploy/disk/example/snapshot/volume-snapshot.yaml +++ b/deploy/disk/example/snapshot/volume-snapshot.yaml @@ -1,18 +1,16 @@ -# +------------------------------------------------------------------------- -# | 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. -# +------------------------------------------------------------------------- +# 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 diff --git a/deploy/disk/example/tag/deploy.yaml b/deploy/disk/example/tag/deploy.yaml new file mode 100644 index 00000000..b4929383 --- /dev/null +++ b/deploy/disk/example/tag/deploy.yaml @@ -0,0 +1,41 @@ +# 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: tag-example +spec: + selector: + matchLabels: + app: tag-example + tier: csi-qingcloud + replicas: 1 + template: + metadata: + labels: + app: tag-example + tier: csi-qingcloud + spec: + containers: + - name: nginx + image: nginx + volumeMounts: + - mountPath: /mnt + name: mypvc + volumes: + - name: mypvc + persistentVolumeClaim: + claimName: pvc-tag + readOnly: false \ No newline at end of file diff --git a/deploy/disk/example/tag/pvc.yaml b/deploy/disk/example/tag/pvc.yaml new file mode 100644 index 00000000..32edc9bf --- /dev/null +++ b/deploy/disk/example/tag/pvc.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: v1 +kind: PersistentVolumeClaim +metadata: + name: pvc-tag +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + storageClassName: csi-qingcloud \ No newline at end of file diff --git a/deploy/disk/example/tag/sc.yaml b/deploy/disk/example/tag/sc.yaml new file mode 100644 index 00000000..7eb82489 --- /dev/null +++ b/deploy/disk/example/tag/sc.yaml @@ -0,0 +1,29 @@ +# 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: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: csi-qingcloud +provisioner: disk.csi.qingcloud.com +parameters: + type: "200" + maxSize: "5000" + minSize: "10" + stepSize: "10" + fsType: "ext4" + replica: "2" + tags: "tag-y7uu1q2a" +reclaimPolicy: Delete +allowVolumeExpansion: true \ No newline at end of file diff --git a/deploy/disk/example/topology/deploy.yaml b/deploy/disk/example/topology/deploy.yaml index 4a1a7057..27ed0648 100644 --- a/deploy/disk/example/topology/deploy.yaml +++ b/deploy/disk/example/topology/deploy.yaml @@ -1,34 +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. -# +------------------------------------------------------------------------- - +# 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-topology + name: topology-example spec: selector: matchLabels: - app: nginx-topology + app: topology-example tier: csi-qingcloud replicas: 1 template: metadata: labels: - app: nginx-topology + app: topology-example tier: csi-qingcloud spec: containers: @@ -41,5 +38,4 @@ spec: - name: mypvc persistentVolumeClaim: claimName: pvc-topology - readOnly: false - + readOnly: false \ No newline at end of file diff --git a/deploy/disk/example/topology/pvc.yaml b/deploy/disk/example/topology/pvc.yaml index 97f18b58..44129a0c 100644 --- a/deploy/disk/example/topology/pvc.yaml +++ b/deploy/disk/example/topology/pvc.yaml @@ -1,19 +1,16 @@ -# +------------------------------------------------------------------------- -# | 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. -# +------------------------------------------------------------------------- - +# 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 @@ -25,4 +22,4 @@ spec: resources: requests: storage: 20Gi - storageClassName: csi-qingcloud + storageClassName: csi-qingcloud \ No newline at end of file diff --git a/deploy/disk/example/topology/sc.yaml b/deploy/disk/example/topology/sc.yaml index e9d193a5..6da86cbf 100644 --- a/deploy/disk/example/topology/sc.yaml +++ b/deploy/disk/example/topology/sc.yaml @@ -1,18 +1,16 @@ -# +------------------------------------------------------------------------- -# | 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. -# +------------------------------------------------------------------------- +# 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: storage.k8s.io/v1 kind: StorageClass diff --git a/deploy/disk/example/volume/deploy.yaml b/deploy/disk/example/volume/deploy.yaml index b2e62d76..447d4dd6 100644 --- a/deploy/disk/example/volume/deploy.yaml +++ b/deploy/disk/example/volume/deploy.yaml @@ -1,34 +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. -# +------------------------------------------------------------------------- - +# 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 + name: volume-expample spec: selector: matchLabels: - app: nginx + app: volume-expample tier: csi-qingcloud replicas: 1 template: metadata: labels: - app: nginx + app: volume-expample tier: csi-qingcloud spec: containers: @@ -40,6 +37,5 @@ spec: volumes: - name: mypvc persistentVolumeClaim: - claimName: pvc-test - readOnly: false - + claimName: pvc-example + readOnly: false \ No newline at end of file diff --git a/deploy/disk/example/volume/pvc.yaml b/deploy/disk/example/volume/pvc.yaml index 916fc15f..d250b317 100644 --- a/deploy/disk/example/volume/pvc.yaml +++ b/deploy/disk/example/volume/pvc.yaml @@ -1,28 +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. -# +------------------------------------------------------------------------- - +# 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-test + name: pvc-example spec: accessModes: - ReadWriteOnce resources: requests: storage: 20Gi - storageClassName: csi-qingcloud + storageClassName: csi-qingcloud \ No newline at end of file diff --git a/deploy/disk/example/volume/sc.yaml b/deploy/disk/example/volume/sc.yaml index 9b86843c..b9841816 100644 --- a/deploy/disk/example/volume/sc.yaml +++ b/deploy/disk/example/volume/sc.yaml @@ -1,18 +1,16 @@ -# +------------------------------------------------------------------------- -# | 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. -# +------------------------------------------------------------------------- +# 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: storage.k8s.io/v1 kind: StorageClass diff --git a/pkg/cloud/cloud_manager.go b/pkg/cloud/cloud_manager.go index 7febec4a..def783b4 100644 --- a/pkg/cloud/cloud_manager.go +++ b/pkg/cloud/cloud_manager.go @@ -104,6 +104,12 @@ type UtilManager interface { GetZone() (zoneName string) // GetZoneList get accessible zone list GetZoneList() (zoneNameList []string, err error) + // FindTags finds and gets tags information + FindTag(tagId string) (tagInfo *qcservice.Tag, err error) + // IsValidTags checks tags exists. + IsValidTags(tagsId []string) bool + // AttachTags add a slice of tags on a object + AttachTags(tagsId []string, resourceId string, resourceType string) (err error) } type CloudManager interface { diff --git a/pkg/cloud/mock/mock_cloud_manager.go b/pkg/cloud/mock/mock_cloud_manager.go index 1a1bf533..fca22453 100644 --- a/pkg/cloud/mock/mock_cloud_manager.go +++ b/pkg/cloud/mock/mock_cloud_manager.go @@ -201,3 +201,18 @@ func (m *mockCloudManager) GetZoneList() (zoneNameList []string, err error) { func (m *mockCloudManager) waitJob(jobId string) (err error) { return nil } + +// FindTags finds and gets tags information +func (m *mockCloudManager) FindTag(tagId string) (tagInfo *qcservice.Tag, err error) { + return nil, nil +} + +// IsValidTags checks tags exists. +func (m *mockCloudManager) IsValidTags(tagsId []string) bool { + return false +} + +// AttachTags add a slice of tags on a object +func (m *mockCloudManager) AttachTags(tagsId []string, resourceId string, resourceType string) (err error) { + return nil +} diff --git a/pkg/cloud/qingcloud_manager.go b/pkg/cloud/qingcloud_manager.go index 28542d60..a2234a23 100644 --- a/pkg/cloud/qingcloud_manager.go +++ b/pkg/cloud/qingcloud_manager.go @@ -33,6 +33,7 @@ type qingCloudManager struct { volumeService *qcservice.VolumeService jobService *qcservice.JobService cloudService *qcservice.QingCloudService + tagService *qcservice.TagService } func NewQingCloudManagerFromConfig(config *qcconfig.Config) (*qingCloudManager, error) { @@ -46,6 +47,7 @@ func NewQingCloudManagerFromConfig(config *qcconfig.Config) (*qingCloudManager, ss, _ := qs.Snapshot(config.Zone) vs, _ := qs.Volume(config.Zone) js, _ := qs.Job(config.Zone) + ts, _ := qs.Tag(config.Zone) // initial cloud manager cm := qingCloudManager{ @@ -54,6 +56,7 @@ func NewQingCloudManagerFromConfig(config *qcconfig.Config) (*qingCloudManager, volumeService: vs, jobService: js, cloudService: qs, + tagService: ts, } klog.Infof("Succeed to initial cloud manager") return &cm, nil @@ -557,6 +560,75 @@ func (zm *qingCloudManager) GetZoneList() (zones []string, err error) { return zones, nil } +// FindTags finds and gets tags information +func (cm *qingCloudManager) FindTag(tagId string) (tagInfo *qcservice.Tag, err error) { + if len(tagId) == 0 { + return nil, nil + } + input := &qcservice.DescribeTagsInput{ + Tags: []*string{&tagId}, + } + + output, err := cm.tagService.DescribeTags(input) + if err != nil { + return nil, err + } + if *output.RetCode != 0 { + klog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return nil, fmt.Errorf("call IaaS DescribeTags err: tag id %s in %s", tagId, cm.GetZone()) + } + switch *output.TotalCount { + // Not found tag + case 0: + return nil, nil + // Found one tag + case 1: + return output.TagSet[0], nil + // Found duplicate tags + default: + return nil, fmt.Errorf("call IaaS DescribeTags err: find duplicate tags, tag id %s in %s", + tagId, cm.GetZone()) + } +} + +// AttachTag adds a slice of tags on a object +func (cm *qingCloudManager) AttachTags(tagsId []string, resourceId string, resourceType string) (err error) { + tagPairs := []*qcservice.ResourceTagPair{} + for index := range tagsId { + tagPairs = append(tagPairs, &qcservice.ResourceTagPair{ + ResourceID: &resourceId, + ResourceType: &resourceType, + TagID: &tagsId[index], + }) + } + input := &qcservice.AttachTagsInput{tagPairs} + output, err := cm.tagService.AttachTags(input) + if err != nil { + return err + } + if *output.RetCode != 0 { + klog.Errorf("Ret code: %d, message: %s", *output.RetCode, *output.Message) + return fmt.Errorf("call IaaS AttachTags err: tag id %v, resource id %s, resource type %s in %s", tagsId, + resourceId, resourceType, cm.GetZone()) + } + klog.Infof("Call IaaS AttachTags %v on resource %s succeed", tagsId, resourceId) + return nil +} + +// IsValidTags checks tags exists. +func (cm *qingCloudManager) IsValidTags(tagsId []string) bool { + for _, tagId := range tagsId { + tagInfo, err := cm.FindTag(tagId) + if err != nil { + return false + } + if tagInfo == nil { + return false + } + } + return true +} + func (cm *qingCloudManager) waitJob(jobId string) error { err := qcclient.WaitJob(cm.jobService, jobId, WaitJobTimeout, WaitJobInterval) if err != nil { diff --git a/pkg/cloud/qingcloud_manager_test.go b/pkg/cloud/qingcloud_manager_test.go index 00f5c10e..e26df255 100644 --- a/pkg/cloud/qingcloud_manager_test.go +++ b/pkg/cloud/qingcloud_manager_test.go @@ -1,3 +1,19 @@ +/* +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 cloud import ( @@ -215,3 +231,116 @@ func TestQingCloudManager_CreateVolumeFromSnapshot(t *testing.T) { } } } + +const ( + tagId = "tag-qaf8td3d" + tagId2 = "tag-1phpmfym" + tagIdInOtherZone = "tag-glozcqzd" + resourceId = "vol-zqxq0i9k" + resourceType = "volume" + resourceIdInOtherZone = "vol-t6jgk2fp" +) + +func TestQingCloudManager_FindTag(t *testing.T) { + tests := []struct { + name string + tagId string + foundTag bool + isError bool + }{ + { + name: "valid tag", + tagId: tagId, + foundTag: true, + isError: false, + }, + { + name: "other zone", + tagId: tagIdInOtherZone, + foundTag: false, + isError: false, + }, + } + for _, v := range tests { + tagInfo, err := cfg.FindTag(v.tagId) + if (tagInfo != nil) != v.foundTag && (err == nil) != v.isError { + t.Errorf("name %s, expect [%t,%t], but actually [%t,%t]", v.name, v.foundTag, v.isError, + tagInfo != nil, err == nil) + } + } +} + +func TestQingCloudManager_IsValidTags(t *testing.T) { + tests := []struct { + name string + tagId []string + isValid bool + }{ + { + name: "multiple tags", + tagId: []string{tagId, tagId2}, + isValid: true, + }, + { + name: "single tags", + tagId: []string{tagId}, + isValid: true, + }, + { + name: "tags in other zone", + tagId: []string{tagIdInOtherZone}, + isValid: false, + }, + } + for _, test := range tests { + res := cfg.IsValidTags(test.tagId) + if test.isValid != res { + t.Errorf("name %s, expect %t, but actually %t", test.name, test.isValid, res) + } + } +} + +func TestQingCloudManager_AttachTags(t *testing.T) { + tests := []struct { + name string + tagId []string + resourceId string + resourceType string + isError bool + }{ + { + name: "add multiple tags", + tagId: []string{tagId, tagId2}, + resourceId: resourceId, + resourceType: ResourceTypeVolume, + isError: false, + }, + { + name: "re-attach tags", + tagId: []string{tagId, tagId2}, + resourceId: resourceId, + resourceType: ResourceTypeVolume, + isError: false, + }, + { + name: "attach other zone resource", + tagId: []string{tagId}, + resourceId: resourceIdInOtherZone, + resourceType: ResourceTypeVolume, + isError: true, + }, + { + name: "invalid resource type", + tagId: []string{tagId}, + resourceId: resourceId, + resourceType: ResourceTypeSnapshot, + isError: true, + }, + } + for _, v := range tests { + err := cfg.AttachTags(v.tagId, v.resourceId, v.resourceType) + if (err != nil) != v.isError { + t.Errorf("name %s, expect %t, but actually %t", v.name, v.isError, err != nil) + } + } +} diff --git a/pkg/cloud/types.go b/pkg/cloud/types.go index 60f0631c..cbab485e 100644 --- a/pkg/cloud/types.go +++ b/pkg/cloud/types.go @@ -103,3 +103,8 @@ const ( EnableDescribeInstanceVerboseMode = 1 DisableDescribeInstanceVerboseMode = 0 ) + +const ( + ResourceTypeVolume = "volume" + ResourceTypeSnapshot = "snapshot" +) diff --git a/pkg/common/calculator.go b/pkg/common/calculator.go index 1a2637a3..54b45ce0 100644 --- a/pkg/common/calculator.go +++ b/pkg/common/calculator.go @@ -24,45 +24,49 @@ import ( // GibToByte // Convert GiB to Byte -func GibToByte(num int) int64 { - return int64(num) * Gib +func GibToByte(nGib int) int64 { + return int64(nGib) * Gib } // ByteCeilToGib // Convert Byte to Gib -func ByteCeilToGib(num int64) int { - if num <= 0 { +func ByteCeilToGib(nByte int64) int { + if nByte <= 0 { return 0 } - res := num / Gib - if res*Gib < num { + res := nByte / Gib + if res*Gib < nByte { res += 1 } return int(res) } // Valid capacity bytes in capacity range -func IsValidCapacityBytes(cur int64, capRanges *csi.CapacityRange) bool { - if capRanges == nil { +func IsValidCapacityBytes(cur int64, capRange *csi.CapacityRange) bool { + if capRange == nil { return true } - if capRanges.GetRequiredBytes() > 0 && cur < capRanges.GetRequiredBytes() { + if capRange.GetRequiredBytes() > 0 && cur < capRange.GetRequiredBytes() { return false } - if capRanges.GetLimitBytes() > 0 && cur > capRanges.GetLimitBytes() { + if capRange.GetLimitBytes() > 0 && cur > capRange.GetLimitBytes() { return false } return true } +// GetRequestSizeBytes get minimal required bytes and not exceed limit bytes. func GetRequestSizeBytes(capRange *csi.CapacityRange) (int64, error) { if capRange == nil { return 0, nil } requiredBytes := capRange.GetRequiredBytes() - limitBytes := capRange.GetLimitBytes() + if requiredBytes < 0 || limitBytes < 0 { + return -1, fmt.Errorf("capacity range [%d,%d] should not less than zero", requiredBytes, limitBytes) + } + if limitBytes == 0 { limitBytes = math.MaxInt64 } diff --git a/pkg/common/calculator_test.go b/pkg/common/calculator_test.go new file mode 100644 index 00000000..672e67a1 --- /dev/null +++ b/pkg/common/calculator_test.go @@ -0,0 +1,227 @@ +/* +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 common + +import ( + "github.com/container-storage-interface/spec/lib/go/csi" + "testing" +) + +func TestGibToByte(t *testing.T) { + tests := []struct { + name string + gib int + bytes int64 + }{ + { + name: "normal", + gib: 23, + bytes: 23 * Gib, + }, + { + name: "large number", + gib: 65536000, + bytes: 65536000 * Gib, + }, + { + name: "zero Gib", + gib: 0, + bytes: 0, + }, + { + name: "minus Gib", + gib: -24, + bytes: -24 * Gib, + }, + } + for _, test := range tests { + res := GibToByte(test.gib) + if test.bytes != res { + t.Errorf("name %s: expect %d, but actually %d", test.name, test.bytes, res) + } + } +} + +func TestByteCeilToGib(t *testing.T) { + tests := []struct { + name string + nByte int64 + nGib int + }{ + { + name: "normal", + nByte: 3 * Gib, + nGib: 3, + }, + { + name: "ceil to gib", + nByte: 3*Gib + 3, + nGib: 4, + }, + { + name: "zero bytes", + nByte: 0, + nGib: 0, + }, + { + name: "minus value", + nByte: -1, + nGib: 0, + }, + } + for _, test := range tests { + res := ByteCeilToGib(test.nByte) + if test.nGib != res { + t.Errorf("name %s: expect %d, but actually %d", test.name, test.nGib, res) + } + } +} + +func TestIsValidCapacityBytes(t *testing.T) { + tests := []struct { + name string + bytes int64 + capRange *csi.CapacityRange + isValid bool + }{ + { + name: "normal", + bytes: 10 * Gib, + capRange: &csi.CapacityRange{ + RequiredBytes: 10 * Gib, + LimitBytes: 10 * Gib, + }, + isValid: true, + }, + { + name: "invalid range", + bytes: 10 * Gib, + capRange: &csi.CapacityRange{ + RequiredBytes: 11 * Gib, + LimitBytes: 10 * Gib, + }, + isValid: false, + }, + { + name: "empty range", + bytes: 10 * Gib, + capRange: &csi.CapacityRange{}, + isValid: true, + }, + { + name: "nil range", + bytes: 10 * Gib, + capRange: nil, + isValid: true, + }, + { + name: "without floor", + bytes: 10 * Gib, + capRange: &csi.CapacityRange{ + LimitBytes: 10*Gib + 1, + }, + isValid: true, + }, + { + name: "invalid floor", + bytes: 11 * Gib, + capRange: &csi.CapacityRange{ + RequiredBytes: 11*Gib + 1, + }, + isValid: false, + }, + { + name: "without ceil", + bytes: 14 * Gib, + capRange: &csi.CapacityRange{ + RequiredBytes: 14 * Gib, + }, + isValid: true, + }, + { + name: "invalid ceil", + bytes: 14 * Gib, + capRange: &csi.CapacityRange{ + LimitBytes: 14*Gib - 1, + }, + isValid: false, + }, + } + for _, test := range tests { + res := IsValidCapacityBytes(test.bytes, test.capRange) + if test.isValid != res { + t.Errorf("name %s: expect %t, but actually %t", test.name, test.isValid, res) + } + } +} + +func TestGetRequestSizeBytes(t *testing.T) { + tests := []struct { + name string + capRange *csi.CapacityRange + nBytes int64 + }{ + { + name: "normal", + capRange: &csi.CapacityRange{ + RequiredBytes: 10 * Gib, + LimitBytes: 10 * Gib, + }, + nBytes: 10 * Gib, + }, + { + name: "empty range", + capRange: &csi.CapacityRange{}, + nBytes: 0, + }, + { + name: "nil range", + capRange: nil, + nBytes: 0, + }, + { + name: "normal range 2", + capRange: &csi.CapacityRange{ + RequiredBytes: 23 * Gib, + LimitBytes: 25 * Gib, + }, + nBytes: 23 * Gib, + }, + { + name: "invalid range", + capRange: &csi.CapacityRange{ + RequiredBytes: 23 * Gib, + LimitBytes: 21 * Gib, + }, + nBytes: -1, + }, + { + name: "less than zero", + capRange: &csi.CapacityRange{ + RequiredBytes: -23 * Gib, + LimitBytes: -21 * Gib, + }, + nBytes: -1, + }, + } + for _, test := range tests { + res, _ := GetRequestSizeBytes(test.capRange) + if test.nBytes != res { + t.Errorf("name %s: expect %d, but actually %d", test.name, test.nBytes, res) + } + } +} diff --git a/pkg/disk/driver/driver_test.go b/pkg/disk/driver/driver_test.go index 2df7cb09..e9b8625e 100644 --- a/pkg/disk/driver/driver_test.go +++ b/pkg/disk/driver/driver_test.go @@ -1,3 +1,19 @@ +/* +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 ( diff --git a/pkg/disk/driver/snapshotclass.go b/pkg/disk/driver/snapshotclass.go new file mode 100644 index 00000000..e13a3af4 --- /dev/null +++ b/pkg/disk/driver/snapshotclass.go @@ -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. +*/ + +package driver + +import "strings" + +const ( + SnapshotClassTagsName = "tags" +) + +type QingSnapshotClass struct { + Tags []string +} + +// NewDefaultQingSnapshotClass create default QingSnapshotClass object +func NewDefaultQingSnapshotClass() *QingSnapshotClass { + return &QingSnapshotClass{} +} + +func NewQingSnapshotClassFromMap(opt map[string]string) (*QingSnapshotClass, error) { + sTags, tagsOk := opt[SnapshotClassTagsName] + + sc := NewDefaultQingSnapshotClass() + if tagsOk && len(sTags) > 0 { + sc.Tags = strings.Split(strings.ReplaceAll(sTags, " ", ""), ",") + } + return sc, nil +} + +func (sc QingSnapshotClass) GetTags() []string { + return sc.Tags +} diff --git a/pkg/disk/driver/snapshotclass_test.go b/pkg/disk/driver/snapshotclass_test.go new file mode 100644 index 00000000..6ce872e1 --- /dev/null +++ b/pkg/disk/driver/snapshotclass_test.go @@ -0,0 +1,79 @@ +/* +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 ( + "reflect" + "testing" +) + +func TestNewDefaultQingSnapshotClass(t *testing.T) { + tests := []struct { + name string + opt map[string]string + sc *QingSnapshotClass + isError bool + }{ + { + name: "normal", + opt: map[string]string{ + "tags": "tag-glozcqzd, tag-y7uu1q2a", + }, + sc: &QingSnapshotClass{ + Tags: []string{"tag-glozcqzd", "tag-y7uu1q2a"}, + }, + isError: false, + }, + { + name: "normal", + opt: map[string]string{ + "tags": "tag-glozcqzd, tag-y7uu1q2a,tag-y7uuweea", + }, + sc: &QingSnapshotClass{ + Tags: []string{"tag-glozcqzd", "tag-y7uu1q2a", "tag-y7uuweea"}, + }, + isError: false, + }, + { + name: "empty", + opt: map[string]string{ + "tags": "", + }, + sc: &QingSnapshotClass{ + Tags: nil, + }, + isError: false, + }, + { + name: "unset tags", + opt: map[string]string{}, + sc: &QingSnapshotClass{ + Tags: nil, + }, + isError: false, + }, + } + for _, test := range tests { + res, err := NewQingSnapshotClassFromMap(test.opt) + if (err != nil) != test.isError { + t.Errorf("name %s: expect %t but actually %t", test.name, test.isError, err != nil) + } + if !reflect.DeepEqual(test.sc, res) { + t.Errorf("name %s: expect %v but actually %v", test.name, test.sc, res) + } + } +} diff --git a/pkg/disk/driver/storageclass.go b/pkg/disk/driver/storageclass.go index 3995f283..9846a12c 100644 --- a/pkg/disk/driver/storageclass.go +++ b/pkg/disk/driver/storageclass.go @@ -22,20 +22,32 @@ import ( "github.com/yunify/qingcloud-csi/pkg/cloud" "github.com/yunify/qingcloud-csi/pkg/common" "strconv" + "strings" +) + +const ( + StorageClassTypeName = "type" + StorageClassMaxSizeName = "maxSize" + StorageClassMinSizeName = "minSize" + StorageClassStepSizeName = "stepSize" + StorageClassFsTypeName = "fsType" + StorageClassReplicaName = "replica" + StorageClassTagsName = "tags" ) type QingStorageClass struct { - DiskType VolumeType `json:"type"` - MaxSize int `json:"maxSize"` - MinSize int `json:"minSize"` - StepSize int `json:"stepSize"` - FsType string `json:"fsType"` - Replica int `json:"replica"` + DiskType VolumeType + MaxSize int + MinSize int + StepSize int + FsType string + Replica int + Tags []string } -// NewDefaultQingStorageClass create default qingStorageClass object +// NewDefaultQingStorageClass create default QingStorageClass object func NewDefaultQingStorageClass() *QingStorageClass { - return NewDefaultQingStorageClassFromType(SSDEnterpriseVolumeType) + return NewDefaultQingStorageClassFromType(DefaultVolumeType) } // NewDefaultQingStorageClassFromType create default qingStorageClass by specified volume type @@ -55,22 +67,29 @@ func NewDefaultQingStorageClassFromType(diskType VolumeType) *QingStorageClass { // 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 + sVolType, volTypeOk := opt[StorageClassTypeName] + sMaxSize, maxSizeOk := opt[StorageClassMaxSizeName] + sMinSize, minSizeOk := opt[StorageClassMinSizeName] + sStepSize, stepSizeOk := opt[StorageClassStepSizeName] + sFsType, fsTypeOk := opt[StorageClassFsTypeName] + sReplica, replicaOk := opt[StorageClassReplicaName] + sTags, tagsOk := opt[StorageClassTagsName] + + sc := NewDefaultQingStorageClass() + + if volTypeOk { + // Convert volume type to integer + iVolType, err := strconv.Atoi(sVolType) + if err != nil { + return nil, err + } + if !VolumeType(iVolType).IsValid() { + return nil, fmt.Errorf("invalid volume type %d", iVolType) + } + sc.DiskType = VolumeType(iVolType) } - sc := NewDefaultQingStorageClassFromType(VolumeType(iVolType)) - if maxSizeOk == true && minSizeOk == true && stepSizeOk == true { + + if maxSizeOk && minSizeOk && stepSizeOk { // Get volume max size iMaxSize, err := strconv.Atoi(sMaxSize) if err != nil { @@ -104,7 +123,7 @@ func NewQingStorageClassFromMap(opt map[string]string) (*QingStorageClass, error sc.StepSize = iStepSize } - if fsTypeOk == true { + if fsTypeOk { if !IsValidFileSystemType(sFsType) { return nil, fmt.Errorf("unsupported filesystem type %s", sFsType) } @@ -112,7 +131,7 @@ func NewQingStorageClassFromMap(opt map[string]string) (*QingStorageClass, error } // Get volume replicas - if replicaOk == true { + if replicaOk { iReplica, err := strconv.Atoi(sReplica) if err != nil { return nil, err @@ -123,6 +142,9 @@ func NewQingStorageClassFromMap(opt map[string]string) (*QingStorageClass, error sc.Replica = iReplica } + if tagsOk && len(sTags) > 0 { + sc.Tags = strings.Split(strings.ReplaceAll(sTags, " ", ""), ",") + } return sc, nil } @@ -137,6 +159,10 @@ func (sc QingStorageClass) GetStepSizeByte() int64 { return int64(sc.StepSize) * common.Gib } +func (sc QingStorageClass) GetTags() []string { + return sc.Tags +} + // FormatVolumeSize transfer to proper volume size func (sc QingStorageClass) FormatVolumeSizeByte(sizeByte int64) int64 { if sizeByte <= sc.GetMinSizeByte() { diff --git a/pkg/disk/driver/storageclass_test.go b/pkg/disk/driver/storageclass_test.go new file mode 100644 index 00000000..21e7a6aa --- /dev/null +++ b/pkg/disk/driver/storageclass_test.go @@ -0,0 +1,251 @@ +/* +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/yunify/qingcloud-csi/pkg/cloud" + "github.com/yunify/qingcloud-csi/pkg/common" + "reflect" + "strconv" + "testing" +) + +func TestNewDefaultQingStorageClassFromType(t *testing.T) { + tests := []struct { + name string + diskType VolumeType + sc *QingStorageClass + }{ + { + name: "normal", + diskType: DefaultVolumeType, + sc: &QingStorageClass{ + DiskType: DefaultVolumeType, + MaxSize: VolumeTypeToMaxSize[DefaultVolumeType], + MinSize: VolumeTypeToMinSize[DefaultVolumeType], + StepSize: VolumeTypeToStepSize[DefaultVolumeType], + FsType: common.DefaultFileSystem, + Replica: cloud.DefaultDiskReplicaType, + }, + }, + { + name: "number", + diskType: 2, + sc: &QingStorageClass{ + DiskType: HighCapacityVolumeType, + MaxSize: VolumeTypeToMaxSize[HighCapacityVolumeType], + MinSize: VolumeTypeToMinSize[HighCapacityVolumeType], + StepSize: VolumeTypeToStepSize[HighCapacityVolumeType], + FsType: common.DefaultFileSystem, + Replica: cloud.DefaultDiskReplicaType, + }, + }, + { + name: "invalid volume type", + diskType: 99, + sc: nil, + }, + } + for _, test := range tests { + res := NewDefaultQingStorageClassFromType(test.diskType) + if !reflect.DeepEqual(test.sc, res) { + t.Errorf("name %s: expect %v, but actually %v", test.name, test.sc, res) + } + } +} + +func TestNewQingStorageClassFromMap(t *testing.T) { + tests := []struct { + name string + opt map[string]string + sc *QingStorageClass + isError bool + }{ + { + name: "normal", + opt: map[string]string{ + StorageClassTypeName: strconv.Itoa(int(DefaultVolumeType)), + StorageClassMaxSizeName: strconv.Itoa(VolumeTypeToMaxSize[DefaultVolumeType]), + StorageClassMinSizeName: strconv.Itoa(VolumeTypeToMinSize[DefaultVolumeType]), + StorageClassStepSizeName: strconv.Itoa(VolumeTypeToStepSize[DefaultVolumeType]), + StorageClassFsTypeName: common.FileSystemExt3, + StorageClassReplicaName: strconv.Itoa(cloud.DiskSingleReplicaType), + }, + sc: &QingStorageClass{ + DiskType: DefaultVolumeType, + MaxSize: VolumeTypeToMaxSize[DefaultVolumeType], + MinSize: VolumeTypeToMinSize[DefaultVolumeType], + StepSize: VolumeTypeToStepSize[DefaultVolumeType], + FsType: common.FileSystemExt3, + Replica: cloud.DiskSingleReplicaType, + }, + isError: false, + }, + { + name: "empty tag", + opt: map[string]string{ + StorageClassTypeName: strconv.Itoa(int(DefaultVolumeType)), + StorageClassMaxSizeName: strconv.Itoa(VolumeTypeToMaxSize[DefaultVolumeType]), + StorageClassMinSizeName: strconv.Itoa(VolumeTypeToMinSize[DefaultVolumeType]), + StorageClassStepSizeName: strconv.Itoa(VolumeTypeToStepSize[DefaultVolumeType]), + StorageClassFsTypeName: common.FileSystemExt3, + StorageClassReplicaName: strconv.Itoa(cloud.DiskSingleReplicaType), + StorageClassTagsName: "", + }, + sc: &QingStorageClass{ + DiskType: DefaultVolumeType, + MaxSize: VolumeTypeToMaxSize[DefaultVolumeType], + MinSize: VolumeTypeToMinSize[DefaultVolumeType], + StepSize: VolumeTypeToStepSize[DefaultVolumeType], + FsType: common.FileSystemExt3, + Replica: cloud.DiskSingleReplicaType, + }, + isError: false, + }, + { + name: "one tag", + opt: map[string]string{ + StorageClassTypeName: strconv.Itoa(int(DefaultVolumeType)), + StorageClassMaxSizeName: strconv.Itoa(VolumeTypeToMaxSize[DefaultVolumeType]), + StorageClassMinSizeName: strconv.Itoa(VolumeTypeToMinSize[DefaultVolumeType]), + StorageClassStepSizeName: strconv.Itoa(VolumeTypeToStepSize[DefaultVolumeType]), + StorageClassFsTypeName: common.FileSystemExt3, + StorageClassReplicaName: strconv.Itoa(cloud.DiskSingleReplicaType), + StorageClassTagsName: "tag-12345567", + }, + sc: &QingStorageClass{ + DiskType: DefaultVolumeType, + MaxSize: VolumeTypeToMaxSize[DefaultVolumeType], + MinSize: VolumeTypeToMinSize[DefaultVolumeType], + StepSize: VolumeTypeToStepSize[DefaultVolumeType], + FsType: common.FileSystemExt3, + Replica: cloud.DiskSingleReplicaType, + Tags: []string{"tag-12345567"}, + }, + isError: false, + }, + { + name: "multiple tags", + opt: map[string]string{ + StorageClassTypeName: strconv.Itoa(int(DefaultVolumeType)), + StorageClassMaxSizeName: strconv.Itoa(VolumeTypeToMaxSize[DefaultVolumeType]), + StorageClassMinSizeName: strconv.Itoa(VolumeTypeToMinSize[DefaultVolumeType]), + StorageClassStepSizeName: strconv.Itoa(VolumeTypeToStepSize[DefaultVolumeType]), + StorageClassFsTypeName: common.FileSystemExt3, + StorageClassReplicaName: strconv.Itoa(cloud.DiskSingleReplicaType), + StorageClassTagsName: "tag-12345567,tag-22345567, tag-32345567 ", + }, + sc: &QingStorageClass{ + DiskType: DefaultVolumeType, + MaxSize: VolumeTypeToMaxSize[DefaultVolumeType], + MinSize: VolumeTypeToMinSize[DefaultVolumeType], + StepSize: VolumeTypeToStepSize[DefaultVolumeType], + FsType: common.FileSystemExt3, + Replica: cloud.DiskSingleReplicaType, + Tags: []string{"tag-12345567", "tag-22345567", "tag-32345567"}, + }, + isError: false, + }, + } + for _, test := range tests { + res, err := NewQingStorageClassFromMap(test.opt) + if (err != nil) != test.isError { + t.Errorf("name %s: expect %t, but actually %t", test.name, test.isError, err != nil) + } + if !reflect.DeepEqual(test.sc, res) { + t.Errorf("name %s: expect %v, but actually %v", test.name, test.sc, res) + } + } +} + +func TestQingStorageClass_FormatVolumeSizeByte(t *testing.T) { + sc := NewDefaultQingStorageClassFromType(SSDEnterpriseVolumeType) + tests := []struct { + name string + inputSize int64 + formatSize int64 + }{ + { + name: "normal", + inputSize: 123 * common.Gib, + formatSize: 130 * common.Gib, + }, + { + name: "ceil", + inputSize: 2001 * common.Gib, + formatSize: 2000 * common.Gib, + }, + { + name: "minus", + inputSize: -1 * common.Gib, + formatSize: 10 * common.Gib, + }, + } + for _, test := range tests { + res := sc.FormatVolumeSizeByte(test.inputSize) + if test.formatSize != res { + t.Errorf("name %s: expect %d, but actually %d", test.name, test.formatSize, res) + } + } +} + +func TestQingStorageClass_GetRequiredVolumeSizeByte(t *testing.T) { + sc := NewDefaultQingStorageClassFromType(SSDEnterpriseVolumeType) + tests := []struct { + name string + capRrange *csi.CapacityRange + size int64 + isError bool + }{ + { + name: "normal", + capRrange: &csi.CapacityRange{ + RequiredBytes: 20 * common.Gib, + LimitBytes: 20 * common.Gib, + }, + size: 20 * common.Gib, + isError: false, + }, + { + name: "without limit", + capRrange: &csi.CapacityRange{ + RequiredBytes: 23 * common.Gib, + }, + size: 30 * common.Gib, + isError: false, + }, + { + name: "failed", + capRrange: &csi.CapacityRange{ + RequiredBytes: 2 * common.Gib, + LimitBytes: 4 * common.Gib, + }, + size: -1, + isError: true, + }, + } + for _, test := range tests { + res, err := sc.GetRequiredVolumeSizeByte(test.capRrange) + if (err != nil) != test.isError { + t.Errorf("name %s: expect %t, but actually %t", test.name, test.isError, err != nil) + } + if test.size != res { + t.Errorf("name %s: expect %d, but actually %d", test.name, test.size, res) + } + } +} diff --git a/pkg/disk/driver/topology.go b/pkg/disk/driver/topology.go index e7c632fb..fb9e9243 100644 --- a/pkg/disk/driver/topology.go +++ b/pkg/disk/driver/topology.go @@ -1,3 +1,19 @@ +/* +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 type Topology struct { diff --git a/pkg/disk/driver/types.go b/pkg/disk/driver/types.go index ecc12259..135c86f4 100644 --- a/pkg/disk/driver/types.go +++ b/pkg/disk/driver/types.go @@ -66,6 +66,7 @@ var DefaultPluginCapability = []*csi.PluginCapability{ } const ( + DefaultVolumeType VolumeType = SSDEnterpriseVolumeType HighPerformanceVolumeType VolumeType = 0 HighCapacityVolumeType VolumeType = 2 SuperHighPerformanceVolumeType VolumeType = 3 diff --git a/pkg/disk/rpcserver/controllerserver.go b/pkg/disk/rpcserver/controllerserver.go index 64209b9c..f65e5e42 100644 --- a/pkg/disk/rpcserver/controllerserver.go +++ b/pkg/disk/rpcserver/controllerserver.go @@ -97,7 +97,11 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } + if cs.cloud.IsValidTags(sc.GetTags()) == false { + return nil, status.Errorf(codes.InvalidArgument, "Invalid tags in storage class %v", sc.GetTags()) + } klog.Infof("Create storage class %v", sc) + // get request volume capacity range requiredSizeByte, err := sc.GetRequiredVolumeSizeByte(req.GetCapacityRange()) if err != nil { @@ -161,6 +165,13 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } klog.Infof("%s: Succeed to create empty volume [%s/%s].", hash, volName, newVolId) + + err = cs.cloud.AttachTags(sc.GetTags(), newVolId, cloud.ResourceTypeVolume) + if err != nil { + klog.Errorf("%s: Failed to add tags %v on %s %s", hash, sc.GetTags(), cloud.ResourceTypeVolume, + newVolId) + return nil, status.Errorf(codes.Internal, err.Error()) + } return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ VolumeId: newVolId, @@ -223,6 +234,12 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol "expected volume size [%d], but actually [%d], volume id [%s], snapshot id [%s]", requiredRestoreVolumeSizeInBytes, actualRestoreVolumeSizeInBytes, newVolId, snapId) } + err = cs.cloud.AttachTags(sc.GetTags(), newVolId, cloud.ResourceTypeVolume) + if err != nil { + klog.Errorf("%s: Failed to add tags %v on %s %s", hash, sc.GetTags(), cloud.ResourceTypeVolume, + newVolId) + return nil, status.Errorf(codes.Internal, err.Error()) + } return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ VolumeId: newVolId, @@ -349,6 +366,12 @@ func (cs *ControllerServer) ControllerPublishVolume(ctx context.Context, req *cs // Volume published to another node if len(*exVol.Instance.InstanceID) != 0 { if *exVol.Instance.InstanceID == nodeId { + if *exVol.Instance.Device != "" { + // cannot found device path + klog.Errorf("The plugin SHOULD NOT run here, please report at " + + "https://github.com/yunify/qingcloud-csi.") + return nil, status.Errorf(codes.Aborted, "operation pending for volume %s detaching", volumeId) + } klog.Warningf("volume %s has been already attached on instance %s", volumeId, nodeId) return &csi.ControllerPublishVolumeResponse{}, nil } else { @@ -392,8 +415,7 @@ func (cs *ControllerServer) ControllerPublishVolume(ctx context.Context, req *cs // Try to detach volume klog.Infof("Cannot find device path and going to detach volume %s", volumeId) if err := cs.cloud.DetachVolume(volumeId, nodeId); err != nil { - return nil, status.Errorf(codes.Internal, - "cannot find device path, detach volume %s failed", volumeId) + return nil, status.Errorf(codes.Internal, "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.", @@ -578,18 +600,20 @@ 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) { - klog.Info("----- Start CreateSnapshot -----") - defer klog.Info("===== End CreateSnapshot =====") +func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) (*csi. + CreateSnapshotResponse, error) { + funcName := "CreateSnapshot" + info, hash := common.EntryFunction(funcName) + klog.Info(info) + defer klog.Info(common.ExitFunction(funcName, hash)) + // 0. Prepare if isValid := cs.driver.ValidateControllerServiceRequest(csi. ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT); isValid != true { klog.Errorf("invalid create snapshot request: %v", req) return nil, status.Error(codes.Unimplemented, "") } - // 0. Preflight // Check source volume id - klog.Info("Check required parameters") + klog.Infof("%s: Check required parameters", hash) if len(req.GetSourceVolumeId()) == 0 { return nil, status.Error(codes.InvalidArgument, "volume ID missing in request") } @@ -607,13 +631,13 @@ func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS // 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. - klog.Infof("Find existing snapshot name [%s]", snapName) + klog.Infof("%s: Find existing snapshot name [%s]", hash, 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()) } if exSnap != nil { - klog.Infof("Found existing snapshot name [%s], snapshot id [%s], source volume id %s", + klog.Infof("%s: Found existing snapshot name [%s], snapshot id [%s], source volume id %s", hash, *exSnap.SnapshotName, *exSnap.SnapshotID, *exSnap.Resource.ResourceID) if exSnap.Resource != nil && *exSnap.Resource.ResourceType == "volume" && *exSnap.Resource.ResourceID == srcVolId { @@ -621,6 +645,7 @@ func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS if err != nil { return nil, status.Error(codes.Internal, err.Error()) } + klog.Infof("%s: Get snapshot %s status %s", hash, snapName, *exSnap.Status) if *exSnap.Status == cloud.SnapshotStatusAvailable { isReadyToUse = true } else { @@ -640,23 +665,40 @@ func (cs *ControllerServer) CreateSnapshot(ctx context.Context, req *csi.CreateS "snapshot name [%s] id [%s] already exists, but is incompatible with the source volume id [%s]", snapName, *exSnap.SnapshotID, srcVolId) } + // Create snapshot class for add tags + klog.Infof("%s: Try to create snapshot class from %v", hash, req.GetParameters()) + sc, err := driver.NewQingSnapshotClassFromMap(req.GetParameters()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + if cs.cloud.IsValidTags(sc.GetTags()) == false { + return nil, status.Errorf(codes.InvalidArgument, "Invalid tags in storage class %v", sc.GetTags()) + } + klog.Infof("%s: Succeed to create snapshot class %v", hash, sc) // Create a new full snapshot - klog.Infof("Creating snapshot [%s] from volume [%s] in zone [%s]...", snapName, srcVolId, cs.cloud.GetZone()) - snapId, err := cs.cloud.CreateSnapshot(snapName, srcVolId) + klog.Infof("%s: Creating snapshot [%s] from volume [%s] in zone [%s]...", hash, snapName, srcVolId, + cs.cloud.GetZone()) + newSnapId, 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()) } - klog.Infof("Create snapshot [%s] finished, get snapshot id [%s]", snapName, snapId) - klog.Infof("Get snapshot id [%s] info...", snapId) - snapInfo, err := cs.cloud.FindSnapshot(snapId) + klog.Infof("%s: Create snapshot [%s] finished, get snapshot id [%s]", hash, snapName, newSnapId) + err = cs.cloud.AttachTags(sc.GetTags(), newSnapId, cloud.ResourceTypeSnapshot) + if err != nil { + klog.Errorf("%s: Failed to add tags %v on %s %s", hash, sc.GetTags(), cloud.ResourceTypeVolume, + newSnapId) + return nil, status.Errorf(codes.Internal, err.Error()) + } + klog.Infof("%s: Get snapshot id [%s] info...", hash, newSnapId) + snapInfo, err := cs.cloud.FindSnapshot(newSnapId) if err != nil { - return nil, status.Errorf(codes.Internal, "Find snapshot [%s] error: %s", snapId, err.Error()) + return nil, status.Errorf(codes.Internal, "Find snapshot [%s] error: %s", newSnapId, err.Error()) } if snapInfo == nil { - return nil, status.Errorf(codes.Internal, "cannot find just created snapshot id [%s]", snapId) + return nil, status.Errorf(codes.Internal, "cannot find just created snapshot id [%s]", newSnapId) } - klog.Infof("Succeed to find snapshot id [%s]", snapId) + klog.Infof("%s: Succeed to find snapshot id [%s]", hash, newSnapId) ts, err = ptypes.TimestampProto(*snapInfo.CreateTime) if err != nil { return nil, status.Error(codes.Internal, err.Error()) diff --git a/pkg/disk/rpcserver/controllerserver_test.go b/pkg/disk/rpcserver/controllerserver_test.go index 13c4cd88..10ab35a6 100644 --- a/pkg/disk/rpcserver/controllerserver_test.go +++ b/pkg/disk/rpcserver/controllerserver_test.go @@ -1,3 +1,19 @@ +/* +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 rpcserver import (