diff --git a/.gitignore b/.gitignore index 173454be..8aac5e8a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ *.iml out gen +go.sum +docs/site/public +docs/site/resources +docs/site/.hugo_build.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 360b717c..968d9bcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,12 @@ [Releases](https://github.com/Huawei/eSDK_K8S_Plugin/releases) -## Changes since v4.3.0 +## v4.5.0 + +- The default synchronization speed of hyper metro pair is changed from the highest speed to the default speed determined by the storage. +- Fixed the semaphore timeout issue when a large number of PVs fail to be mounted. + +## v4.4.0 - Support Kubernetes 1.30 - Support OceanStor 6.1.8 @@ -12,7 +17,7 @@ - The new feature Modify Volume allows a normal PV to be changed to a HyperMetro PV - Create VolumeSnapshot and Clone Persistent Volume support HyperMetro PV -## Changes since v4.2.0 +## v4.3.0 - Support UltraPath 31.2.1/NVMe over RoCE on Rocky Linux 8.6 X86_64 - Support OpenShift 4.14 @@ -32,7 +37,7 @@ - Support configuring requests and limits of container - The log directory of the oceanctl tool can be configured -## Changes since v4.1.0 +## v4.2.0 - Support OpenShift 4.13 - Support Centos 8.4 X86_64 @@ -42,7 +47,7 @@ - Support configuring the timeout for executing commands - Support create volume snapshot for Hyper-Metro -## Changes since v4.0.0 +## v4.1.0 **Enhancements** @@ -52,7 +57,7 @@ - Support Kylin 7.6 x86_64 - The number of path groups aggregated by DM-multipath can be configured -## Changes since v3.2.x +## v4.0.0 **Enhancements** @@ -66,7 +71,7 @@ - Support k8s 1.26 - Upgrade using go 1.18 -## Changes since v3.1.0 +## v3.2.0 **Enhancements** @@ -79,7 +84,7 @@ - Support Ubuntu 22.04, SUSE 15 SP3 - Support EulerOS V2R10 -## Changes since v3.0.0 +## v3.1.0 **Enhancements** @@ -90,7 +95,7 @@ - Support k8s 1.19 and 1.20 - Support Debian 11 -## Changes since v2.2.16 +## v3.0.0 **Enhancements** @@ -105,7 +110,7 @@ - Support OceanStor V6 6.1.5 - Support OceanStor Pacific 8.1.3 -## Changes since v2.2.15 +## v2.2.16 **Enhancements** diff --git a/CI/.cloudbuild/build.yml b/CI/.cloudbuild/build.yml deleted file mode 100644 index 86d4a123..00000000 --- a/CI/.cloudbuild/build.yml +++ /dev/null @@ -1,89 +0,0 @@ - ---- -version: 2.0 - -# 构建环境 - - -# 构建参数定义, 构建脚本可从环境变量中读取使用这些参数 -params: - - name: upload_to_Cloudcmc - value: 'false' - - name: upload_to_Enterprisecmc - value: 'false' - - name: RELEASE_VER - value: 2.3.2 - - name: VER - value: 2.2.13.4 - - name: PLATFORM - value: X86 - - name: esdk_ci_branch - value: master - - name: DRCSI_branch - value: master - - name: dockerimg - value: 'szvecr02.his.huawei.com:80/ecr-build/esdk_suse_x86_12sp5:2.1.RC1' - - -env: - resource: - type: docker - image: ${dockerimg} - resource_class: 4U4G - pool: docker-gz-x86-ondocker-16u-01 - -steps: - PRE_BUILD: - - checkout: - path: eSDK_Enterprise_Storage_Kubernetes - - codehub: - url: https://codehub-dg-y.huawei.com/esdk/esdk_public/esdk_ci.git - branch: ${esdk_ci_branch} - path: esdk_ci - - codehub: - url: https://codehub-dg-y.huawei.com/esdk/XuanWu/Public/DRCSI/DRCSI.git - branch: ${DRCSI_branch} - path: DRCSI - BUILD: - - build_execute: - command: sh eSDK_Enterprise_Storage_Kubernetes/CI/build.sh ${RELEASE_VER} ${VER} ${PLATFORM};echo "buildVersion=${eSDK_version}.$(date "+%Y%m%d%H%M%S")" > buildInfo.properties - accelerate: false - check: true - POST_BUILD: - - get_build_metadata - - upload_cloud_artifact: - file_path: 'eSDK_Enterprise_Storage_Kubernetes/*.zip*' - - version_set - - when: - condition: upload_to_Cloudcmc == 'true' - steps: - - get_build_metadata - - artget: - artifact_type: cmcbinary - action: push - dependency: eSDK_Enterprise_Storage_Kubernetes/CI/conf/cmc_dependency.xml - version_output_path: . - username: ${cmc_username} - password: ${cmc_password} - agent: . - cache: /home/ - add_source_code: true - params: {'version':'${Cloudversion}','dir':'eSDK_Enterprise_Storage_Kubernetes/eSDK_Huawei_Storage*.zip*','dist':'${dist}','offering':'eSDK Cloud Storage Plugins'} - - get_build_metadata - - when: - condition: upload_to_Enterprisecmc == 'true' - steps: - - get_build_metadata - - artget: - artifact_type: cmcbinary - action: push - dependency: eSDK_Enterprise_Storage_Kubernetes/CI/conf/cmc_dependency.xml - version_output_path: . - username: ${cmc_username} - password: ${cmc_password} - agent: . - cache: /home/ - add_source_code: true - params: {'version':'${Enterpriseversion}','dir':'eSDK_Enterprise_Storage_Kubernetes/eSDK_Huawei_Storage*.zip*','dist':'${dist}','offering':'eSDK Enterprise Storage Plugins'} - - get_build_metadata - diff --git a/CI/build.sh b/CI/build.sh deleted file mode 100644 index 5d635028..00000000 --- a/CI/build.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash -# -# Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -export GOPROXY=http://mirrors.tools.huawei.com/goproxy/ - -if [ ${PLATFORM} == X86 ];then - wget http://10.29.160.97/busybox-x86.tar - wget http://10.29.160.97/gcr-x86.tar - docker load -i busybox-x86.tar > imageidfile - docker load -i gcr-x86.tar > imageidfile1 - imageId=$(cat imageidfile|awk '{print $3}') - imageId1=$(cat imageidfile1|awk '{print $3}') - docker tag busybox:1.36.1 busybox:stable-glibc - docker tag ${imageId1} gcr.io/distroless/base:latest -elif [ ${PLATFORM} == ARM ]; then - wget http://10.29.160.97/busybox-arm.tar - wget http://10.29.160.97/gcr-arm.tar - docker load -i busybox-arm.tar > imageidfile - docker load -i gcr-arm.tar > imageidfile1 - imageId=$(cat imageidfile|awk '{print $3}') - imageId1=$(cat imageidfile1|awk '{print $3}') - docker tag busybox:1.36.1 busybox:stable-glibc - docker tag ${imageId1} gcr.io/distroless/base:latest -fi - -package_name="eSDK_Huawei_Storage_${RELEASE_VER}_Kubernetes_CSI_Plugin_V${VER}_${PLATFORM}_64" - -cd eSDK_Enterprise_Storage_Kubernetes -go mod tidy || go mod tidy -make -f Makefile RELEASE_VER=$1 VER=$2 PLATFORM=$3 - -# ------------------------------------------------------------------------------- -echo "Platform confirmation" -if [[ "${PLATFORM}" == "ARM" ]];then - PULL_FLAG="--platform=arm64" - BUILD_FLAG="--platform linux/arm64" -elif [[ "${PLATFORM}" == "X86" ]];then - PULL_FLAG="--platform=amd64" - BUILD_FLAG="--platform linux/amd64" -else - echo "Wrong PLATFORM, support [X86, ARM]" - exit -fi - -rm -rf build_dir -rm -f ./huawei-csi -rm -f ./storage-backend-controller -rm -f ./storage-backend-sidecar -rm -f ./huawei-csi-extender -unzip -d build_dir -q ${package_name}.zip -mv build_dir/${package_name}/bin/huawei-csi ./ -mv build_dir/${package_name}/bin/storage-backend-controller ./ -mv build_dir/${package_name}/bin/storage-backend-sidecar ./ -mv build_dir/${package_name}/bin/huawei-csi-extender ./ - -docker build ${BUILD_FLAG} --build-arg VERSION=${VER} --target huawei-csi-driver -f Dockerfile -t huawei-csi:${VER} . -docker build ${BUILD_FLAG} --build-arg VERSION=${VER} --target storage-backend-controller -f Dockerfile -t storage-backend-controller:${VER} . -docker build ${BUILD_FLAG} --build-arg VERSION=${VER} --target storage-backend-sidecar -f Dockerfile -t storage-backend-sidecar:${VER} . -docker build ${BUILD_FLAG} --build-arg VERSION=${VER} --target huawei-csi-extender -f Dockerfile -t huawei-csi-extender:${VER} . - - -plat=$(echo ${PLATFORM}|tr 'A-Z' 'a-z') -docker save huawei-csi:${VER} -o huawei-csi-v${VER}-${plat}.tar -docker save storage-backend-controller:${VER} -o storage-backend-controller-v${VER}-${plat}.tar -docker save storage-backend-sidecar:${VER} -o storage-backend-sidecar-v${VER}-${plat}.tar -docker save huawei-csi-extender:${VER} -o huawei-csi-extender-v${VER}-${plat}.tar - - -mkdir build_dir/${package_name}/image -mv huawei-csi-v${VER}-${plat}.tar build_dir/${package_name}/image -mv storage-backend-controller-v${VER}-${plat}.tar build_dir/${package_name}/image -mv storage-backend-sidecar-v${VER}-${plat}.tar build_dir/${package_name}/image -mv huawei-csi-extender-v${VER}-${plat}.tar build_dir/${package_name}/image -# ------------------------------------------------------------------------------- - -rm -rf ${package_name}.zip -cd build_dir -zip -rq ../${package_name}.zip * -# 签名 -cd .. -mkdir sign -mv ${package_name}.zip sign -sh esdk_ci/ci/build_product_signature.sh $(pwd)/sign -mkdir cms -mv sign/*.cms . -sh esdk_ci/ci/build_product_signature_hwp7s.sh $(pwd)/sign -mv sign/* ${WORKSPACE}/eSDK_Enterprise_Storage_Kubernetes \ No newline at end of file diff --git a/CI/conf/cmc_dependency.xml b/CI/conf/cmc_dependency.xml deleted file mode 100644 index a77c7ba9..00000000 --- a/CI/conf/cmc_dependency.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - BVersion - Generic - - ${offering} - ${version} - - N - - - ${dir} - ${dist} - - - - - - BVersion - Generic - - eSDK Enterprise Storage Plugins - eSDK Enterprise Storage Plugins 2.3.RC1.B040 - - - - pkg - - - - - - \ No newline at end of file diff --git a/Makefile b/Makefile index 0b4c162c..4c719d68 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,10 @@ PLATFORM=PLATFORM RELEASE_VER=RELEASE_VER # (Optional) [TRUE FALSE] Compile Binary Only, Cancel Inline Optimization ONLY_BIN=ONLY_BIN +# (Optional) [github] Specifies the platform which to build on +BUILD_ON=BUILD_ON export GO111MODULE=on -export GOPATH:=$(GOPATH):$(shell pwd) ifeq (${RELEASE_VER}, RELEASE_VER) export PACKAGE=eSDK_Huawei_Storage_Kubernetes_CSI_Plugin_V${VER}_${PLATFORM}_64 @@ -24,16 +25,26 @@ ifeq (${ONLY_BIN}, TRUE) all:PREPARE BUILD PACK # Disable inline optimization flag = -gcflags "all=-N -l" +binary_flag = -gcflags "all=-N -l" else -flag = -ldflags="-s" -buildmode=pie +flag = -ldflags="-s -linkmode 'external' -extldflags '-Wl,-z,now'" -buildmode=pie +binary_flag = -ldflags="-s" -buildmode=pie all:PREPARE BUILD COPY_FILE PACK endif # Platform [X86, ARM] ifeq (${PLATFORM}, X86) -env = CGO_ENABLED=0 GOOS=linux GOARCH=amd64 +env = CGO_CFLAGS="-fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2" GOOS=linux GOARCH=amd64 +binary_env = CGO_ENABLED=0 GOOS=linux GOARCH=amd64 else -env = CGO_ENABLED=0 GOOS=linux GOARCH=arm64 +env = CGO_CFLAGS="-fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2" GOOS=linux GOARCH=arm64 +binary_env = CGO_ENABLED=0 GOOS=linux GOARCH=arm64 +endif + +# Build_ON [github] +ifeq ($(BUILD_ON), github) +flag = -ldflags="-s -bindnow" -buildmode=pie +env = $(binary_env) endif PREPARE: @@ -42,11 +53,12 @@ PREPARE: BUILD: # usage: [env] go build [-o output] [flags] packages + go mod tidy ${env} go build -o ./${PACKAGE}/bin/huawei-csi ${flag} ./csi ${env} go build -o ./${PACKAGE}/bin/storage-backend-controller ${flag} ./cmd/storage-backend-controller ${env} go build -o ./${PACKAGE}/bin/storage-backend-sidecar ${flag} ./cmd/storage-backend-sidecar ${env} go build -o ./${PACKAGE}/bin/huawei-csi-extender ${flag} ./cmd/huawei-csi-extender - ${env} go build -o ./${PACKAGE}/bin/oceanctl ${flag} ./cli + ${binary_env} go build -o ./${PACKAGE}/bin/oceanctl ${binary_flag} ./cli COPY_FILE: mkdir -p ./${PACKAGE}/examples diff --git a/build.sh b/build.sh index f913d9da..ff7ae735 100644 --- a/build.sh +++ b/build.sh @@ -25,7 +25,7 @@ PLATFORM=$2 package_name="eSDK_Huawei_Storage_Kubernetes_CSI_Plugin_V${VER}_${PLATFORM}_64" echo "Start to make with Makefile" -make -f Makefile VER=$1 PLATFORM=$2 +make -f Makefile VER=$1 PLATFORM=$2 BUILD_ON=github echo "Platform confirmation" if [[ "${PLATFORM}" == "ARM" ]];then diff --git a/cli/client/client_helper.go b/cli/client/client_helper.go index fc18acb4..cd4244d4 100644 --- a/cli/client/client_helper.go +++ b/cli/client/client_helper.go @@ -21,10 +21,10 @@ import ( "encoding/json" "errors" "fmt" + "path" "reflect" corev1 "k8s.io/api/core/v1" - k8string "k8s.io/utils/strings" "huawei-csi-driver/cli/helper" xuanwuV1 "huawei-csi-driver/client/apis/xuanwu/v1" @@ -86,7 +86,7 @@ func (r *CommonCallHandler[T]) DeleteByNames(namespace string, names ...string) return err } for _, name := range names { - qualifiedNames = append(qualifiedNames, k8string.JoinQualifiedName(string(resourceType), name)) + qualifiedNames = append(qualifiedNames, path.Join(string(resourceType), name)) } _, err = r.client.DeleteResourceByQualifiedNames(qualifiedNames, namespace) return err diff --git a/cli/client/client_helper_test.go b/cli/client/client_helper_test.go index 07f408b7..6a11bca1 100644 --- a/cli/client/client_helper_test.go +++ b/cli/client/client_helper_test.go @@ -23,7 +23,7 @@ import ( "testing" "github.com/agiledragon/gomonkey/v2" - "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -32,11 +32,11 @@ func TestGetObjectType_get_namespace_type(t *testing.T) { // arrange var mockObject corev1.Namespace var except = Namespace - convey.Convey("test get_namespace_type", t, func() { + t.Run("test get_namespace_type", func(t *testing.T) { // action objectType := getObjectType(&mockObject) // assert - convey.So(objectType, convey.ShouldResemble, except) + require.Equal(t, except, objectType) }) } @@ -44,11 +44,11 @@ func TestGetObjectType_get_node_type(t *testing.T) { // arrange var mockObject corev1.NodeList var except = Node - convey.Convey("test get_node_type", t, func() { + t.Run("test get_node_type", func(t *testing.T) { // action objectType := getObjectType(&mockObject) // assert - convey.So(objectType, convey.ShouldResemble, except) + require.Equal(t, except, objectType) }) } @@ -56,11 +56,11 @@ func TestGetObjectType_get_pod_type(t *testing.T) { // arrange var mockObject corev1.Pod var except = Pod - convey.Convey("test get_pod_type", t, func() { + t.Run("test get_pod_type", func(t *testing.T) { // action objectType := getObjectType(&mockObject) // assert - convey.So(objectType, convey.ShouldResemble, except) + require.Equal(t, except, objectType) }) } @@ -68,11 +68,11 @@ func TestGetObjectType_get_unknown_type(t *testing.T) { // arrange var mockObject interface{} var except = Unknown - convey.Convey("test get_unknown_type", t, func() { + t.Run("test get_unknown_type", func(t *testing.T) { // action objectType := getObjectType(&mockObject) // assert - convey.So(objectType, convey.ShouldResemble, except) + require.Equal(t, except, objectType) }) } @@ -97,12 +97,12 @@ func TestCommonCallHandler_CheckObjectExist_check_exist_namespace(t *testing.T) }) defer patches.Reset() - convey.Convey("test check_exist_namespace", t, func() { + t.Run("test check_exist_namespace", func(t *testing.T) { // action exist, err := mockCli.CheckObjectExist(context.Background(), mockNamespace, mockNodeName, mockObjectName) // assert - convey.So(exist, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, except, exist) }) } @@ -123,12 +123,12 @@ func TestCommonCallHandler_CheckObjectExist_check_not_exist_node(t *testing.T) { }) defer patches.Reset() - convey.Convey("test check_not_exist_node", t, func() { + t.Run("test check_not_exist_node", func(t *testing.T) { // action exist, err := mockCli.CheckObjectExist(context.Background(), mockNamespace, mockNodeName, mockObjectName) // assert - convey.So(exist, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, except, exist) }) } @@ -165,11 +165,11 @@ func TestCommonCallHandler_GetObject_get_podList(t *testing.T) { }) defer patches.Reset() - convey.Convey("test get pod list", t, func() { + t.Run("test get pod list", func(t *testing.T) { // action object, err := mockCli.GetObject(context.Background(), mockNamespace, mockNodeName) // assert - convey.So(object, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, except, object) }) } diff --git a/cli/client/kube_client_test.go b/cli/client/kube_client_test.go index 44cf4224..ac10c7fb 100644 --- a/cli/client/kube_client_test.go +++ b/cli/client/kube_client_test.go @@ -21,7 +21,7 @@ import ( "encoding/json" "testing" - "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" coreV1 "k8s.io/api/core/v1" ) @@ -42,7 +42,7 @@ func TestKubernetesCLI_GetObject_success(t *testing.T) { patches := mockExecReturnStdOut("kubectl get pod huawei-csi-node-9lxhm -n huawei-csi -o=json") defer patches.Reset() - convey.Convey("test get object success", t, func() { + t.Run("test get object success", func(t *testing.T) { // action if mockCli == nil { t.Errorf("mockCLi is nil") @@ -52,8 +52,8 @@ func TestKubernetesCLI_GetObject_success(t *testing.T) { mockObjectName) //assert - convey.So(mockData, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, except, mockData) }) } @@ -69,7 +69,7 @@ func TestKubernetesCLI_GetObject_failed_without_objectType(t *testing.T) { patches := mockExecReturnStdOut("kubectl get pod huawei-csi-node-9lxhm -n huawei-csi -o=json") defer patches.Reset() - convey.Convey("test get object success", t, func() { + t.Run("test get object success", func(t *testing.T) { // action if mockCli == nil { t.Errorf("mockCLi is nil") @@ -79,8 +79,8 @@ func TestKubernetesCLI_GetObject_failed_without_objectType(t *testing.T) { mockObjectName) //assert - convey.So(mockData, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) + require.Equal(t, except, mockData) }) } @@ -95,7 +95,7 @@ func TestKubernetesCLI_CopyContainerFileToLocal_success(t *testing.T) { "/tmp/slave1/a.tar -c huawei-csi-driver") defer patches.Reset() - convey.Convey("test copy file from container to local", t, func() { + t.Run("test copy file from container to local", func(t *testing.T) { // action if mockCli == nil { t.Errorf("mockCLi is nil") @@ -105,8 +105,8 @@ func TestKubernetesCLI_CopyContainerFileToLocal_success(t *testing.T) { mockObjectName) //assert - convey.So(out, convey.ShouldResemble, []byte(returnStr)) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, []byte(returnStr), out) }) } @@ -120,7 +120,7 @@ func TestKubernetesCLI_GetConsoleLogs_success(t *testing.T) { patches := mockExecReturnStdOut("kubectl logs huawei-csi-node-9lxhm -c huawei-csi-driver -n huawei-csi") defer patches.Reset() - convey.Convey("test get container console logs", t, func() { + t.Run("test get container console logs", func(t *testing.T) { // action if mockCli == nil { t.Errorf("mockCLi is nil") @@ -129,8 +129,8 @@ func TestKubernetesCLI_GetConsoleLogs_success(t *testing.T) { out, err := mockCli.GetConsoleLogs(mockCtx, mockNamespace, mockContainerName, false, mockObjectName) //assert - convey.So(out, convey.ShouldResemble, []byte(returnStr)) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, []byte(returnStr), out) }) } @@ -145,7 +145,7 @@ func TestKubernetesCLI_ExecCmdInSpecifiedContainer_success(t *testing.T) { "-n huawei-csi -- collect.sh") defer patches.Reset() - convey.Convey("test exec script in container", t, func() { + t.Run("test exec script in container", func(t *testing.T) { // action if mockCli == nil { t.Errorf("mockCLi is nil") @@ -155,7 +155,7 @@ func TestKubernetesCLI_ExecCmdInSpecifiedContainer_success(t *testing.T) { mockObjectName) //assert - convey.So(out, convey.ShouldResemble, []byte(returnStr)) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, []byte(returnStr), out) }) } diff --git a/cli/client/kubectl_options.go b/cli/client/kubectl_options.go index 4a6bb018..848404a8 100644 --- a/cli/client/kubectl_options.go +++ b/cli/client/kubectl_options.go @@ -22,7 +22,7 @@ import ( "errors" "fmt" - "github.com/ghodss/yaml" + "gopkg.in/yaml.v3" "huawei-csi-driver/cli/helper" ) diff --git a/cli/client/kubectl_options_test.go b/cli/client/kubectl_options_test.go index fa8eb094..d48390c2 100644 --- a/cli/client/kubectl_options_test.go +++ b/cli/client/kubectl_options_test.go @@ -25,7 +25,7 @@ import ( "testing" "github.com/agiledragon/gomonkey/v2" - "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" coreV1 "k8s.io/api/core/v1" ) @@ -69,13 +69,13 @@ func TestKubernetesCLIArgs_Get_failed_without_options(t *testing.T) { patches := mockExecReturnStdOut("kubectl get pod huawei-csi-node-9lxhm -n huawei-csi -o=json") defer patches.Reset() - convey.Convey("test get object failed without options", t, func() { + t.Run("test get object failed without options", func(t *testing.T) { // action err := mockArgs.Get(mockCtx, &mockData) //assert - convey.So(mockData, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) + require.Equal(t, except, mockData) }) } @@ -96,13 +96,13 @@ func TestKubernetesCLIArgs_Get_success(t *testing.T) { patches := mockExecReturnStdOut("kubectl get pod huawei-csi-node-9lxhm -n huawei-csi -o=json") defer patches.Reset() - convey.Convey("test get object success", t, func() { + t.Run("test get object success", func(t *testing.T) { // action err := mockArgs.Get(mockCtx, &mockData) //assert - convey.So(mockData, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, except, mockData) }) } @@ -120,13 +120,13 @@ func TestKubernetesCLIArgs_Exec_success(t *testing.T) { "-n huawei-csi -- collect.sh") defer patches.Reset() - convey.Convey("test exec cmd in container success", t, func() { + t.Run("test exec cmd in container success", func(t *testing.T) { // action out, err := mockArgs.Exec(mockCtx, mockCmd) // assert - convey.So(out, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, except, out) }) } @@ -145,13 +145,13 @@ func TestKubernetesCLIArgs_Copy_success(t *testing.T) { "/tmp/slave1/a.tar -c huawei-csi-driver") defer patches.Reset() - convey.Convey("test copy file from container to local success", t, func() { + t.Run("test copy file from container to local success", func(t *testing.T) { // action out, err := mockArgs.Copy(mockCtx, mockContainerPath, mockLocalPath, mockCopyType) // assert - convey.So(out, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, except, out) }) } @@ -167,12 +167,12 @@ func TestKubernetesCLIArgs_Logs(t *testing.T) { patches := mockExecReturnStdOut("kubectl logs huawei-csi-node-9lxhm -c huawei-csi-driver -n huawei-csi") defer patches.Reset() - convey.Convey("test get container console logs success", t, func() { + t.Run("test get container console logs success", func(t *testing.T) { // action out, err := mockArgs.Logs(mockCtx) // assert - convey.So(out, convey.ShouldResemble, except) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, except, out) }) } diff --git a/cli/cmd/collect.go b/cli/cmd/collect.go index 912c0191..6472ebdc 100644 --- a/cli/cmd/collect.go +++ b/cli/cmd/collect.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,7 @@ * limitations under the License. */ -// Package command for collecting messages in Kubernetes. -package command +package cmd import ( "github.com/spf13/cobra" @@ -23,7 +22,7 @@ import ( "huawei-csi-driver/cli/cmd/options" ) -func init() { +func registerCollectCmd() { options.NewFlagsOptions(collectCmd).WithParent(RootCmd) } diff --git a/cli/cmd/collect_logs.go b/cli/cmd/collect_logs.go index bf92ab7d..3f379812 100644 --- a/cli/cmd/collect_logs.go +++ b/cli/cmd/collect_logs.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -25,7 +25,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerCollectLogsCmd() { options.NewFlagsOptions(collectLogsCmd). WithNameSpace(true). WithAllNodes(). diff --git a/cli/cmd/create.go b/cli/cmd/create.go index 5145ed49..cc43586e 100644 --- a/cli/cmd/create.go +++ b/cli/cmd/create.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,7 @@ * limitations under the License. */ -// Package command is used to creating a resource to Ocean Storage in Kubernetes. -package command +package cmd import ( "github.com/spf13/cobra" @@ -23,7 +22,7 @@ import ( "huawei-csi-driver/cli/cmd/options" ) -func init() { +func registerCreateCmd() { options.NewFlagsOptions(CreateCmd).WithParent(RootCmd) } diff --git a/cli/cmd/create_backend.go b/cli/cmd/create_backend.go index 95f6fa6a..f26561f6 100644 --- a/cli/cmd/create_backend.go +++ b/cli/cmd/create_backend.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -26,7 +26,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerCreateBackendCmd() { options.NewFlagsOptions(createBackendCmd). WithNameSpace(false). WithFilename(true). diff --git a/cli/cmd/create_cert.go b/cli/cmd/create_cert.go index f6e5b432..2257f88e 100644 --- a/cli/cmd/create_cert.go +++ b/cli/cmd/create_cert.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -26,7 +26,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerCreateCertCmd() { options.NewFlagsOptions(createSecretCmd). WithNameSpace(false). WithFilename(true). diff --git a/cli/cmd/delete.go b/cli/cmd/delete.go index e40136cc..40cef39a 100644 --- a/cli/cmd/delete.go +++ b/cli/cmd/delete.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -22,7 +22,7 @@ import ( "huawei-csi-driver/cli/cmd/options" ) -func init() { +func registerDeleteCmd() { options.NewFlagsOptions(deleteCmd).WithParent(RootCmd) } diff --git a/cli/cmd/delete_backend.go b/cli/cmd/delete_backend.go index 2604ee4b..faf4d348 100644 --- a/cli/cmd/delete_backend.go +++ b/cli/cmd/delete_backend.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -26,7 +26,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerDeleteBackendCmd() { options.NewFlagsOptions(deleteBackendCmd). WithNameSpace(false). WithDeleteAll(). diff --git a/cli/cmd/delete_cert.go b/cli/cmd/delete_cert.go index 9452b1f7..c06bfe4e 100644 --- a/cli/cmd/delete_cert.go +++ b/cli/cmd/delete_cert.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -25,7 +25,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerDeleteCertCmd() { options.NewFlagsOptions(deleteSecretCmd). WithNameSpace(false). WithBackend(true). diff --git a/cli/cmd/get.go b/cli/cmd/get.go index 147daa05..d8346441 100644 --- a/cli/cmd/get.go +++ b/cli/cmd/get.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -22,7 +22,7 @@ import ( "huawei-csi-driver/cli/cmd/options" ) -func init() { +func registerGetCmd() { options.NewFlagsOptions(getCmd).WithParent(RootCmd) } diff --git a/cli/cmd/get_backend.go b/cli/cmd/get_backend.go index 4bee1399..9e139692 100644 --- a/cli/cmd/get_backend.go +++ b/cli/cmd/get_backend.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -26,7 +26,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerGetBackendCmd() { options.NewFlagsOptions(getBackendCmd). WithNameSpace(false). WithOutPutFormat(). diff --git a/cli/cmd/get_cert.go b/cli/cmd/get_cert.go index 88823a3a..128176b2 100644 --- a/cli/cmd/get_cert.go +++ b/cli/cmd/get_cert.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -25,7 +25,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerGetCertCmd() { options.NewFlagsOptions(getSecretCmd). WithNameSpace(false). WithBackend(true). diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 811616b0..9b9101e5 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ * limitations under the License. */ -// Package command defines commands of oceanctl. -package command +// cmd defines commands of oceanctl. +package cmd import ( "path" @@ -44,9 +44,30 @@ var RootCmd = &cobra.Command{ }, } -func init() { - options.NewFlagsOptions(RootCmd). - WithLogDir() +// Execute runs the root command +func Execute() error { + registerRootCmd() + registerCollectCmd() + registerCollectLogsCmd() + registerCreateCmd() + registerCreateBackendCmd() + registerCreateCertCmd() + registerDeleteCmd() + registerDeleteBackendCmd() + registerDeleteCertCmd() + registerGetCmd() + registerGetBackendCmd() + registerGetCertCmd() + registerUpdateCmd() + registerUpdateBackendCmd() + registerUpdateCertCmd() + registerVersionCmd() + + return RootCmd.Execute() +} + +func registerRootCmd() { + options.NewFlagsOptions(RootCmd).WithLogDir() } func discoverOperating() error { @@ -75,7 +96,7 @@ func startLogging() error { if config.LogDir == "" { config.LogDir = config.DefaultLogDir } - logRequest := &log.LoggingRequest{ + logRequest := &log.Config{ LogName: config.DefaultLogName, LogFileSize: config.DefaultLogSize, LoggingModule: config.DefaultLogModule, diff --git a/cli/cmd/update.go b/cli/cmd/update.go index 0fda2a56..db717fd4 100644 --- a/cli/cmd/update.go +++ b/cli/cmd/update.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -22,7 +22,7 @@ import ( "huawei-csi-driver/cli/cmd/options" ) -func init() { +func registerUpdateCmd() { options.NewFlagsOptions(updateCmd).WithParent(RootCmd) } diff --git a/cli/cmd/update_backend.go b/cli/cmd/update_backend.go index 1b967ede..8d77b12c 100644 --- a/cli/cmd/update_backend.go +++ b/cli/cmd/update_backend.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -26,7 +26,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerUpdateBackendCmd() { options.NewFlagsOptions(updateBackendCmd). WithNameSpace(false). WithPassword(true). diff --git a/cli/cmd/update_cert.go b/cli/cmd/update_cert.go index 297c9eb2..ec3badee 100644 --- a/cli/cmd/update_cert.go +++ b/cli/cmd/update_cert.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package command +package cmd import ( "github.com/spf13/cobra" @@ -25,7 +25,7 @@ import ( "huawei-csi-driver/cli/resources" ) -func init() { +func registerUpdateCertCmd() { options.NewFlagsOptions(updateSecretCmd). WithNameSpace(false). WithFilename(true). diff --git a/cli/cmd/version.go b/cli/cmd/version.go index 4b8b2376..80fd3c1c 100644 --- a/cli/cmd/version.go +++ b/cli/cmd/version.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,17 @@ * limitations under the License. */ -package command +package cmd import ( "fmt" + "github.com/spf13/cobra" "huawei-csi-driver/cli/config" ) -func init() { +func registerVersionCmd() { RootCmd.AddCommand(versionCmd) } diff --git a/cli/config/config.go b/cli/config/config.go index da447397..5641acc2 100644 --- a/cli/config/config.go +++ b/cli/config/config.go @@ -23,7 +23,7 @@ import ( const ( // CliVersion oceanctl version - CliVersion = "v4.4.0" + CliVersion = "v4.5.0" // DefaultMaxClientThreads default max client threads DefaultMaxClientThreads = "30" diff --git a/cli/helper/command_helper.go b/cli/helper/command_helper.go index a6d690d1..d6a0a319 100644 --- a/cli/helper/command_helper.go +++ b/cli/helper/command_helper.go @@ -106,20 +106,19 @@ func getInputString(tips string, isVisible bool) (string, error) { } // GetSelectedNumber get the number entered by the user -func GetSelectedNumber(tips string, maxValue int) (int, error) { +func GetSelectedNumber(tips string, maxValue int) (int, bool, error) { input, err := getInputString(tips, true) if err != nil { - return 0, err + return 0, false, err } if strings.ToLower(input) == exitCommand { - os.Exit(0) - return 0, nil + return 0, true, nil } number, err := strconv.Atoi(input) if err == nil && number > 0 && number <= maxValue { - return number, nil + return number, false, nil } fmt.Printf("Input invalid. The valid backend number is [1-%d].\n", maxValue) diff --git a/cli/helper/utils.go b/cli/helper/utils.go index 8f7c4249..4191e25b 100644 --- a/cli/helper/utils.go +++ b/cli/helper/utils.go @@ -319,3 +319,13 @@ func ConvertInterface(i interface{}) interface{} { } return i } + +// SplitQualifiedName Splits a fully qualified name and returns its namespace and name. +// Assumes that the input 'str' has been validated. +func SplitQualifiedName(str string) (string, string) { + parts := strings.Split(str, "/") + if len(parts) < 2 { + return "", str + } + return parts[0], parts[1] +} diff --git a/cli/helper/yaml_helper.go b/cli/helper/yaml_helper.go index 2e85a54d..a73bcc62 100644 --- a/cli/helper/yaml_helper.go +++ b/cli/helper/yaml_helper.go @@ -19,7 +19,7 @@ package helper import ( "encoding/json" - "github.com/ghodss/yaml" + "gopkg.in/yaml.v3" ) // StructToYAML convert struct to yaml diff --git a/cli/main.go b/cli/main.go index 41a55f4c..c98daf6b 100644 --- a/cli/main.go +++ b/cli/main.go @@ -30,7 +30,7 @@ const ( ) func main() { - if err := command.RootCmd.Execute(); err != nil { + if err := cmd.Execute(); err != nil { os.Exit(ExitCodeFailure) } os.Exit(ExitCodeSuccess) diff --git a/cli/resources/backend.go b/cli/resources/backend.go index d61e7f6b..a58d3ef5 100644 --- a/cli/resources/backend.go +++ b/cli/resources/backend.go @@ -19,12 +19,12 @@ package resources import ( "errors" "fmt" + "path" "reflect" "strconv" "strings" corev1 "k8s.io/api/core/v1" - k8string "k8s.io/utils/strings" "huawei-csi-driver/cli/client" "huawei-csi-driver/cli/config" @@ -142,7 +142,7 @@ func (b *Backend) Update() error { secretClient := client.NewCommonCallHandler[corev1.Secret](config.Client) newClaim := oldClaim.DeepCopy() - newClaim.Spec.SecretMeta = k8string.JoinQualifiedName(newClaim.Namespace, nameWithUUid) + newClaim.Spec.SecretMeta = path.Join(newClaim.Namespace, nameWithUUid) if err = storageBackendClaimClient.Update(*newClaim); err != nil { if err := storageBackendClaimClient.Update(oldClaim); err != nil { @@ -155,7 +155,7 @@ func (b *Backend) Update() error { return err } - _, oldSecretName := k8string.SplitQualifiedName(oldClaim.Spec.SecretMeta) + _, oldSecretName := helper.SplitQualifiedName(oldClaim.Spec.SecretMeta) if err := secretClient.DeleteByNames(newClaim.Namespace, oldSecretName); err != nil { log.Errorf("delete old created secret failed, error: %v", err) } @@ -201,7 +201,7 @@ func fetchBackendShows(claims []xuanwuV1.StorageBackendClaim, namespace string) if claim.Status != nil { contentNames = append(contentNames, claim.Status.BoundContentName) } - _, configName := k8string.SplitQualifiedName(claim.Spec.ConfigMapMeta) + _, configName := helper.SplitQualifiedName(claim.Spec.ConfigMapMeta) configmapNames = append(configmapNames, configName) } @@ -236,7 +236,7 @@ func buildBackendShow(claims []xuanwuV1.StorageBackendClaim, contentList []xuanw } } - _, name := k8string.SplitQualifiedName(claim.Spec.ConfigMapMeta) + _, name := helper.SplitQualifiedName(claim.Spec.ConfigMapMeta) if configuration, ok := config[name]; ok { item.ShowWithConfigOption(*configuration) } @@ -248,11 +248,11 @@ func buildBackendShow(claims []xuanwuV1.StorageBackendClaim, contentList []xuanw } func deleteSbcReferenceResources(claim xuanwuV1.StorageBackendClaim) error { - _, secretName := k8string.SplitQualifiedName(claim.Spec.SecretMeta) - _, configmapName := k8string.SplitQualifiedName(claim.Spec.ConfigMapMeta) - _, certSecretName := k8string.SplitQualifiedName(claim.Spec.CertSecret) + _, secretName := helper.SplitQualifiedName(claim.Spec.SecretMeta) + _, configmapName := helper.SplitQualifiedName(claim.Spec.ConfigMapMeta) + _, certSecretName := helper.SplitQualifiedName(claim.Spec.CertSecret) needDeleteFinalizersResources := []string{ - k8string.JoinQualifiedName(string(client.ConfigMap), configmapName), + path.Join(string(client.ConfigMap), configmapName), } err := config.Client.DeleteFinalizersInResourceByQualifiedNames(needDeleteFinalizersResources, claim.Namespace) @@ -261,12 +261,12 @@ func deleteSbcReferenceResources(claim xuanwuV1.StorageBackendClaim) error { } referenceResources := append(needDeleteFinalizersResources, - k8string.JoinQualifiedName(string(client.Secret), secretName), - k8string.JoinQualifiedName(string(client.Storagebackendclaim), claim.Name)) + path.Join(string(client.Secret), secretName), + path.Join(string(client.Storagebackendclaim), claim.Name)) if certSecretName != "" { referenceResources = append(referenceResources, - k8string.JoinQualifiedName(string(client.Secret), certSecretName)) + path.Join(string(client.Secret), certSecretName)) } _, err = config.Client.DeleteResourceByQualifiedNames(referenceResources, claim.Namespace) @@ -329,6 +329,10 @@ func (b *Backend) Create() error { break } + if selectedBackend == nil { + return nil + } + if selectedBackend.Configured { fmt.Printf("backend [%s] has been Configured, please select another\n", selectedBackend.Name) continue @@ -353,7 +357,7 @@ func FetchConfiguredBackends(namespace string) (map[string]*BackendConfiguration } configuredBackend := helper.MapTo(sbcList, func(claim xuanwuV1.StorageBackendClaim) string { - _, name := k8string.SplitQualifiedName(claim.Spec.ConfigMapMeta) + _, name := helper.SplitQualifiedName(claim.Spec.ConfigMapMeta) return name }) @@ -433,13 +437,17 @@ func ConfigOneBackend(backendConfig *BackendConfiguration) error { func selectOneBackend(backendList []*BackendConfiguration) (*BackendConfiguration, error) { printBackendsStatusTable(backendList) - number, err := helper.GetSelectedNumber("Please enter the backend number to configure "+ + number, isExit, err := helper.GetSelectedNumber("Please enter the backend number to configure "+ "(Enter 'exit' to exit):", len(backendList)) if err != nil { log.Errorf("failed to get backend number entered by user. %v", err) return nil, err } + if isExit { + return nil, nil + } + if number > len(backendList) { number = len(backendList) } diff --git a/cli/resources/backend_helper.go b/cli/resources/backend_helper.go index 07674d97..1c097100 100644 --- a/cli/resources/backend_helper.go +++ b/cli/resources/backend_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,14 @@ import ( "errors" "io" "os" + "path" "reflect" "strconv" "strings" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8string "k8s.io/utils/strings" "huawei-csi-driver/cli/helper" xuanwuv1 "huawei-csi-driver/client/apis/xuanwu/v1" @@ -181,8 +181,8 @@ func (b *BackendConfiguration) ToStorageBackendClaimConfig() StorageBackendClaim return StorageBackendClaimConfig{ Name: b.Name, Namespace: b.NameSpace, - ConfigmapMeta: k8string.JoinQualifiedName(b.NameSpace, b.Name), - SecretMeta: k8string.JoinQualifiedName(b.NameSpace, b.Name), + ConfigmapMeta: path.Join(b.NameSpace, b.Name), + SecretMeta: path.Join(b.NameSpace, b.Name), MaxClientThreads: b.MaxClientThreads, Provisioner: b.Provisioner, } @@ -192,7 +192,7 @@ func (b *BackendConfiguration) ToStorageBackendClaimConfig() StorageBackendClaim func (b *BackendConfiguration) ToConfigMapConfig() (ConfigMapConfig, error) { config := struct { Backends BackendConfiguration `json:"backends"` - }{*b} + }{Backends: *b} config.Backends.Parameters.Portals = helper.ConvertInterface(config.Backends.Parameters.Portals) diff --git a/cli/resources/cert.go b/cli/resources/cert.go index 7b9005f6..bdeeee10 100644 --- a/cli/resources/cert.go +++ b/cli/resources/cert.go @@ -19,9 +19,9 @@ package resources import ( "fmt" "os" + "path" corev1 "k8s.io/api/core/v1" - k8string "k8s.io/utils/strings" "huawei-csi-driver/cli/client" "huawei-csi-driver/cli/config" @@ -54,7 +54,7 @@ func (c *Cert) Get() error { return nil } - _, certSecretName := k8string.SplitQualifiedName(claim.Spec.CertSecret) + _, certSecretName := helper.SplitQualifiedName(claim.Spec.CertSecret) if certSecretName == "" { helper.PrintNoResourceCert(claim.Name, claim.Namespace) @@ -85,7 +85,7 @@ func (c *Cert) Delete() error { return nil } - _, certSecretName := k8string.SplitQualifiedName(oldClaim.Spec.CertSecret) + _, certSecretName := helper.SplitQualifiedName(oldClaim.Spec.CertSecret) if certSecretName == "" { helper.PrintNoResourceCert(oldClaim.Name, oldClaim.Namespace) @@ -126,7 +126,7 @@ func (c *Cert) Update() error { return nil } - _, certSecretName := k8string.SplitQualifiedName(claim.Spec.CertSecret) + _, certSecretName := helper.SplitQualifiedName(claim.Spec.CertSecret) if certSecretName == "" { helper.PrintNoResourceCert(claim.Name, claim.Namespace) @@ -187,7 +187,7 @@ func (c *Cert) Create() error { newClaim := oldClaim.DeepCopy() newClaim.Spec.UseCert = true - newClaim.Spec.CertSecret = k8string.JoinQualifiedName(newClaim.Namespace, certConfig.Name) + newClaim.Spec.CertSecret = path.Join(newClaim.Namespace, certConfig.Name) if err = storageBackendClaimClient.Update(*newClaim); err != nil { if err := secretClient.DeleteByNames(newClaim.Namespace, certConfig.Name); err != nil { @@ -221,7 +221,7 @@ func (c *Cert) LoadCertsFromDate(Data []byte) (*CertConfig, error) { func deleteSecretResources(secret corev1.Secret) error { referenceResources := []string{ - k8string.JoinQualifiedName(string(client.Secret), secret.Name), + path.Join(string(client.Secret), secret.Name), } _, err := config.Client.DeleteResourceByQualifiedNames(referenceResources, secret.Namespace) diff --git a/cli/resources/logs.go b/cli/resources/logs.go index 27ff2f69..3b0dedbe 100644 --- a/cli/resources/logs.go +++ b/cli/resources/logs.go @@ -49,6 +49,7 @@ const ( maxTransmissionsNum = 10 maxNodeGoroutineLimit = 1000 + zipFilePermission = 0600 ) var ( @@ -252,7 +253,7 @@ func zipMultiFiles(zipPath string, filePaths ...string) error { if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil { return helper.LogErrorf("create compressed logs directory failed, error: %v", err) } - archive, err := os.OpenFile(zipPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + archive, err := os.OpenFile(zipPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, zipFilePermission) if err != nil { return helper.LogErrorf("create compressed logs file failed, error: %v", err) } diff --git a/cli/resources/logs_helper.go b/cli/resources/logs_helper.go index 922453d6..e781845a 100644 --- a/cli/resources/logs_helper.go +++ b/cli/resources/logs_helper.go @@ -41,10 +41,18 @@ const ( progressBarLength = 100 consoleLogDirectory = "console" + + logFilePermission = 0600 + colorHexCode = 0x1B + displayPeriod = 10 * time.Millisecond ) var ( - identifyPodTypesFunc = make(map[PodType]func(pod *coreV1.Pod) bool) + identifyPodTypesFunc = map[PodType]func(pod *coreV1.Pod) bool{ + CSI: checkCSIPod, + CSM: checkCSMPod, + Xuanwu: checkXuanwuPod, + } xuanwuPodPrefixNameList = []string{"xuanwu-backup-mngt", "xuanwu-backup-service", "xuanwu-base-mngt", "xuanwu-disaster-service", "xuanwu-disaster-mngt", "xuanwu-metadata-service", "xuanwu-volume-service"} ) @@ -91,12 +99,6 @@ type TransmitTask struct { FileLogsCollect } -func init() { - RegisterIdentifyPodTypeFunc(CSI, checkCSIPod) - RegisterIdentifyPodTypeFunc(CSM, checkCSMPod) - RegisterIdentifyPodTypeFunc(Xuanwu, checkXuanwuPod) -} - // Do copy the compressed log file to the local host. func (t *TransmitTask) Do() { _ = t.CopyToLocal(t.namespace, t.nodeName, t.podName, t.containerName) @@ -129,7 +131,9 @@ func (d *Display) Add(prefixDesc string, f func()) { func (d *Display) show() { for idx, display := range d.displayFunc { - fmt.Printf("%s", d.prefixDesc[idx]) + if idx < len(d.prefixDesc) { + fmt.Printf("%s", d.prefixDesc[idx]) + } display() } } @@ -160,21 +164,23 @@ func (d *Display) Show(ctx context.Context) { default: d.resetCursor() d.show() - time.Sleep(10 * time.Millisecond) + time.Sleep(displayPeriod) } } } func (n *Status) getPercent() int { - return int(atomic.LoadInt32(&n.completed)) * 100 / n.total + return int(atomic.LoadInt32(&n.completed)) * progressBarLength / n.total } func (n *Status) getCompletedStr() string { - return fmt.Sprintf("%c[1;40;32m%s%c[0m", 0x1B, strings.Repeat("+", n.getPercent()*progressBarLength/100), 0x1B) + return fmt.Sprintf("%c[1;40;32m%s%c[0m", colorHexCode, + strings.Repeat("+", n.getPercent()*progressBarLength/progressBarLength), colorHexCode) } func (n *Status) getRemainedStr() string { - return fmt.Sprintf("%s", strings.Repeat("-", progressBarLength-n.getPercent()*progressBarLength/100)) + return fmt.Sprintf("%s", strings.Repeat("-", + progressBarLength-n.getPercent()*progressBarLength/progressBarLength)) } // Display displays the pod log collection status of the current node. @@ -270,16 +276,16 @@ func (n *NodeLogCollector) collectPodLogs(pod *coreV1.Pod, onceIdx int) { msg := fmt.Sprintf("Failed to collect [%s] file logs on node [%s], please collect logs manually,"+ " file logs path is [%s]", pod.Name, pod.Spec.NodeName, logPath) n.display.Add("", func() { - fmt.Printf("%c[1;40;31m%s%c[0m\n", 0x1B, msg, 0x1B) + fmt.Printf("%c[1;40;31m%s%c[0m\n", colorHexCode, msg, colorHexCode) }) _ = helper.LogWarningf(ctx, "error: %v", errors.New(msg)) } for idx := range pod.Spec.Containers { container := &pod.Spec.Containers[idx] - getConsoleLogs(ctx, pod.Namespace, container.Name, pod.Name, pod.Spec.NodeName, false) - getConsoleLogs(ctx, pod.Namespace, container.Name, pod.Name, pod.Spec.NodeName, true) - if isRunning { + getConsoleLogs(ctx, getLogArgs(pod.Namespace, container.Name, pod.Name, pod.Spec.NodeName, false)) + getConsoleLogs(ctx, getLogArgs(pod.Namespace, container.Name, pod.Name, pod.Spec.NodeName, true)) + if isRunning && onceIdx < len(n.fileLogsOnce) { n.fileLogsOnce[onceIdx].Do(func() error { fileLogPath, err := getContainerFileLogPaths(container) if err != nil { @@ -316,12 +322,31 @@ func (n *NodeLogCollector) isCollected(fileLogPath string) bool { return false } -func getConsoleLogs(ctx context.Context, namespace, containerName, podName, nodeName string, isHistoryLogs bool) { - logs, err := config.Client.GetConsoleLogs(ctx, namespace, containerName, isHistoryLogs, podName) +type consoleLogArgs struct { + namespace string + containerName string + podName string + nodeName string + isHistoryLogs bool +} + +func getLogArgs(namespace, containerName, podName, nodeName string, isHistoryLogs bool) consoleLogArgs { + return consoleLogArgs{ + namespace: namespace, + containerName: containerName, + podName: podName, + nodeName: nodeName, + isHistoryLogs: isHistoryLogs, + } +} + +func getConsoleLogs(ctx context.Context, logArgs consoleLogArgs) { + logs, err := config.Client.GetConsoleLogs(ctx, + logArgs.namespace, logArgs.containerName, logArgs.isHistoryLogs, logArgs.podName) if err != nil { _ = helper.LogWarningf(ctx, "get container console logs failed, error: %v", err) } else { - err = saveConsoleLog(logs, namespace, podName, containerName, nodeName, isHistoryLogs) + err = saveConsoleLog(logs, logArgs) if err != nil { log.Errorf("save console log failed, error: %v", err) } @@ -337,19 +362,19 @@ func getPodType(pod *coreV1.Pod) PodType { return UnKnow } -func saveConsoleLog(logs []byte, namespace, podName, containerName, nodeName string, isHistoryLogs bool) error { - ctx := context.WithValue(context.Background(), "tag", podName) - fileName := fmt.Sprintf("%s-%s-%s.log", namespace, podName, containerName) - if isHistoryLogs { +func saveConsoleLog(logs []byte, logArgs consoleLogArgs) error { + ctx := context.WithValue(context.Background(), "tag", logArgs.podName) + fileName := fmt.Sprintf("%s-%s-%s.log", logArgs.namespace, logArgs.podName, logArgs.containerName) + if logArgs.isHistoryLogs { fileName = "last-" + fileName } - file, err := os.Create(path.Join(localLogsPrefixPath, nodeName, consoleLogDirectory, fileName)) + file, err := os.Create(path.Join(localLogsPrefixPath, logArgs.nodeName, consoleLogDirectory, fileName)) if err != nil { return helper.LogWarningf(ctx, "create container console log file failed, error: %v", err) } defer file.Close() - err = file.Chmod(0600) + err = file.Chmod(logFilePermission) if err != nil { return helper.LogWarningf(ctx, "set the file permission failed, error: %v", err) } @@ -378,10 +403,11 @@ func getContainerFileLogPaths(container *coreV1.Container) (string, error) { return "", helper.LogWarningf(context.Background(), "get container file log paths failed, error: %v", errors.New("args is nil")) } + const logPathSplitSegment = 2 for _, arg := range container.Args { if strings.HasPrefix(arg, "--log-file-dir=") { logPath := strings.Split(arg, "=") - if len(logPath) != 2 { + if len(logPath) != logPathSplitSegment { return "", helper.LogWarningf(context.Background(), "get container file log paths failed, error: %v", errors.New("log-file-dir is not set correctly")) } diff --git a/cli/resources/logs_helper_test.go b/cli/resources/logs_helper_test.go index 9d470b4e..ebdeca8d 100644 --- a/cli/resources/logs_helper_test.go +++ b/cli/resources/logs_helper_test.go @@ -49,7 +49,7 @@ func Test_saveConsoleLog_Success(t *testing.T) { }) // act - gotErr := saveConsoleLog(logs, namespace, podName, containerName, nodeName, true) + gotErr := saveConsoleLog(logs, getLogArgs(namespace, containerName, podName, nodeName, true)) // assert if gotErr != nil { @@ -79,7 +79,7 @@ func Test_saveConsoleLog_CreateFileFail(t *testing.T) { }) // act - gotErr := saveConsoleLog(logs, namespace, podName, containerName, nodeName, true) + gotErr := saveConsoleLog(logs, getLogArgs(namespace, containerName, podName, nodeName, true)) // assert if !reflect.DeepEqual(gotErr, wantErr) { @@ -114,7 +114,7 @@ func Test_saveConsoleLog_ChmodFileFail(t *testing.T) { }) // act - gotErr := saveConsoleLog(logs, namespace, podName, containerName, nodeName, true) + gotErr := saveConsoleLog(logs, getLogArgs(namespace, containerName, podName, nodeName, true)) // assert if !reflect.DeepEqual(gotErr, wantErr) { @@ -151,7 +151,7 @@ func Test_saveConsoleLog_WriteFileFail(t *testing.T) { }) // act - gotErr := saveConsoleLog(logs, namespace, podName, containerName, nodeName, true) + gotErr := saveConsoleLog(logs, getLogArgs(namespace, containerName, podName, nodeName, true)) // assert if !reflect.DeepEqual(gotErr, wantErr) { diff --git a/cli/resources/resource.go b/cli/resources/resource.go index 26116a77..b20534bf 100644 --- a/cli/resources/resource.go +++ b/cli/resources/resource.go @@ -68,7 +68,7 @@ func NewResourceBuilder() *ResourceBuilder { // Build convert ResourceBuilder to Resource func (b *ResourceBuilder) Build() *Resource { - return &Resource{b} + return &Resource{ResourceBuilder: b} } // NamespaceParam accepts the namespace that these resources should be diff --git a/cli/resources/validate.go b/cli/resources/validate.go index 75fbea0b..d94e3d5b 100644 --- a/cli/resources/validate.go +++ b/cli/resources/validate.go @@ -21,9 +21,8 @@ import ( "fmt" "strings" - "k8s.io/utils/strings/slices" - "huawei-csi-driver/cli/config" + "huawei-csi-driver/utils" ) // Validator is used to validate a Resource object @@ -99,7 +98,7 @@ func (b *ValidatorBuilder) ValidateOutputFormat() *ValidatorBuilder { if b.resource.output == "" { return b } - if !slices.Contains(config.SupportedFormats, b.resource.output) { + if !utils.Contains(config.SupportedFormats, b.resource.output) { b.errs = append(b.errs, fmt.Errorf("unable to match a printer suitable for the output format %s, "+ "allowed formats are: %v", b.resource.output, strings.Join(config.SupportedFormats, ", "))) } diff --git a/client/apis/xuanwu/v1/register.go b/client/apis/xuanwu/v1/register.go index 27700b8c..ba167749 100644 --- a/client/apis/xuanwu/v1/register.go +++ b/client/apis/xuanwu/v1/register.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -51,8 +51,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { &StorageBackendClaimList{}, &StorageBackendContent{}, &StorageBackendContentList{}, - &ResourceTopology{}, - &ResourceTopologyList{}, &VolumeModifyClaim{}, &VolumeModifyClaimList{}, &VolumeModifyContent{}, diff --git a/client/apis/xuanwu/v1/volumemodifycontent.go b/client/apis/xuanwu/v1/volumemodifycontent.go index 1b262691..bdc22621 100644 --- a/client/apis/xuanwu/v1/volumemodifycontent.go +++ b/client/apis/xuanwu/v1/volumemodifycontent.go @@ -72,7 +72,7 @@ type VolumeModifyContentStatus struct { type VolumeModifyContentPhase string const ( - // VolumeModifyContentPending means the VolumeModifyContent has been accepted. + // VolumeModifyContentPending means the VolumeModifyContent has been accepted, // but modify didn't start. VolumeModifyContentPending VolumeModifyContentPhase = "Pending" diff --git a/client/apis/xuanwu/v1/zz_generated.deepcopy.go b/client/apis/xuanwu/v1/zz_generated.deepcopy.go index a002672d..8281e16e 100644 --- a/client/apis/xuanwu/v1/zz_generated.deepcopy.go +++ b/client/apis/xuanwu/v1/zz_generated.deepcopy.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -61,126 +61,6 @@ func (in *Pool) DeepCopy() *Pool { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceInfo) DeepCopyInto(out *ResourceInfo) { - *out = *in - out.TypeMeta = in.TypeMeta - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceInfo. -func (in *ResourceInfo) DeepCopy() *ResourceInfo { - if in == nil { - return nil - } - out := new(ResourceInfo) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceTopology) DeepCopyInto(out *ResourceTopology) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceTopology. -func (in *ResourceTopology) DeepCopy() *ResourceTopology { - if in == nil { - return nil - } - out := new(ResourceTopology) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ResourceTopology) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceTopologyList) DeepCopyInto(out *ResourceTopologyList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ResourceTopology, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceTopologyList. -func (in *ResourceTopologyList) DeepCopy() *ResourceTopologyList { - if in == nil { - return nil - } - out := new(ResourceTopologyList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ResourceTopologyList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceTopologySpec) DeepCopyInto(out *ResourceTopologySpec) { - *out = *in - if in.Tags != nil { - in, out := &in.Tags, &out.Tags - *out = make([]Tag, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceTopologySpec. -func (in *ResourceTopologySpec) DeepCopy() *ResourceTopologySpec { - if in == nil { - return nil - } - out := new(ResourceTopologySpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResourceTopologyStatus) DeepCopyInto(out *ResourceTopologyStatus) { - *out = *in - if in.Tags != nil { - in, out := &in.Tags, &out.Tags - *out = make([]Tag, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceTopologyStatus. -func (in *ResourceTopologyStatus) DeepCopy() *ResourceTopologyStatus { - if in == nil { - return nil - } - out := new(ResourceTopologyStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StorageBackendClaim) DeepCopyInto(out *StorageBackendClaim) { *out = *in @@ -417,24 +297,6 @@ func (in *StorageBackendContentStatus) DeepCopy() *StorageBackendContentStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Tag) DeepCopyInto(out *Tag) { - *out = *in - out.ResourceInfo = in.ResourceInfo - out.Owner = in.Owner - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tag. -func (in *Tag) DeepCopy() *Tag { - if in == nil { - return nil - } - out := new(Tag) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VolumeModifyClaim) DeepCopyInto(out *VolumeModifyClaim) { *out = *in diff --git a/cmd/huawei-csi-extender/main.go b/cmd/huawei-csi-extender/main.go index b39ee2b0..d1b91ffd 100644 --- a/cmd/huawei-csi-extender/main.go +++ b/cmd/huawei-csi-extender/main.go @@ -49,7 +49,7 @@ func main() { if err := app.NewCommand().Execute(); err != nil { logrus.Fatalf("Execute app command failed. error: %v", err) } - err := log.InitLogging(&log.LoggingRequest{ + err := log.InitLogging(&log.Config{ LogName: containerName, LogFileSize: app.GetGlobalConfig().LogFileSize, LoggingModule: app.GetGlobalConfig().LoggingModule, diff --git a/cmd/storage-backend-controller/main.go b/cmd/storage-backend-controller/main.go index 37d3f75d..199c2255 100644 --- a/cmd/storage-backend-controller/main.go +++ b/cmd/storage-backend-controller/main.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -45,13 +45,17 @@ const ( containerName = "storage-backend-controller" eventComponentName = "XuanWu-StorageBackend-Mngt" leaderLockObjectName = "storage-backend-controller" + + backoffDuration = 100 * time.Millisecond + backoffFactor = 1.5 + backoffSteps = 10 ) func main() { if err := app.NewCommand().Execute(); err != nil { logrus.Fatalf("Execute app command failed. error: %v", err) } - err := log.InitLogging(&log.LoggingRequest{ + err := log.InitLogging(&log.Config{ LogName: containerName, LogFileSize: app.GetGlobalConfig().LogFileSize, LoggingModule: app.GetGlobalConfig().LoggingModule, @@ -169,9 +173,9 @@ func ensureCRDExist(ctx context.Context, client *clientSet.Clientset) error { } backoff := wait.Backoff{ - Duration: 100 * time.Millisecond, - Factor: 1.5, - Steps: 10, + Duration: backoffDuration, + Factor: backoffFactor, + Steps: backoffSteps, } if err := wait.ExponentialBackoff(backoff, exist); err != nil { return err diff --git a/cmd/storage-backend-sidecar/main.go b/cmd/storage-backend-sidecar/main.go index d96054d9..fe567598 100644 --- a/cmd/storage-backend-sidecar/main.go +++ b/cmd/storage-backend-sidecar/main.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ const ( eventComponentName = "XuanWu-StorageBackend-Mngt" leaderLockObjectName = "sb-sidecar-" + backoffDuration = 100 * time.Millisecond ) var ( @@ -62,7 +63,7 @@ func main() { logrus.Fatalf("Execute app command failed. error: %v", err) } - err := log.InitLogging(&log.LoggingRequest{ + err := log.InitLogging(&log.Config{ LogName: containerName, LogFileSize: app.GetGlobalConfig().LogFileSize, LoggingModule: app.GetGlobalConfig().LoggingModule, @@ -200,7 +201,7 @@ func ensureCRDExist(ctx context.Context, client *clientSet.Clientset) error { } backoff := wait.Backoff{ - Duration: 100 * time.Millisecond, + Duration: backoffDuration, Factor: 1.5, Steps: 10, } diff --git a/connector/connector.go b/connector/connector.go index 0844add4..61a001da 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,11 +82,11 @@ const ( var ( // connectors is the global map - connectors = map[string]Connector{} + connectors = map[string]VolumeConnector{} ) -// Connector defines the behavior that the connector should have -type Connector interface { +// VolumeConnector defines the behavior that the connector should have +type VolumeConnector interface { // ConnectVolume to mount the source to target path, the source path can be block or nfs // Example: // mount /dev/sdb / @@ -98,18 +98,18 @@ type Connector interface { // DisConnectInfo defines the fields of disconnect volume type DisConnectInfo struct { - Conn Connector + Conn VolumeConnector TgtLun string } // ConnectInfo defines the fields of connect volume type ConnectInfo struct { - Conn Connector + Conn VolumeConnector MappingInfo map[string]interface{} } // GetConnector can get a connector by its type from the global connector map -func GetConnector(ctx context.Context, cType string) Connector { +func GetConnector(ctx context.Context, cType string) VolumeConnector { if cnt, exist := connectors[cType]; exist { return cnt } @@ -118,8 +118,8 @@ func GetConnector(ctx context.Context, cType string) Connector { return nil } -// RegisterConnector is used to register the specific Connector to the global connector map -func RegisterConnector(cType string, cnt Connector) error { +// RegisterConnector is used to register the specific VolumeConnector to the global connector map +func RegisterConnector(cType string, cnt VolumeConnector) error { if _, exist := connectors[cType]; exist { return fmt.Errorf("connector %s already exists", cType) } diff --git a/connector/connector_test.go b/connector/connector_test.go index 86c37820..9da3a6df 100644 --- a/connector/connector_test.go +++ b/connector/connector_test.go @@ -43,18 +43,18 @@ func (s *stubConnector) DisConnectVolume(ctx context.Context, tgtLunWWN string) return nil } -var testConnector Connector = &stubConnector{} +var testConnector VolumeConnector = &stubConnector{} func TestRegisterConnector(t *testing.T) { defer func() { - connectors = map[string]Connector{} + connectors = map[string]VolumeConnector{} }() connectors["fibreChannel"] = testConnector type args struct { cType string - cnt Connector + cnt VolumeConnector } tests := []struct { name string @@ -83,7 +83,7 @@ func TestGetConnector(t *testing.T) { tests := []struct { name string args args - want Connector + want VolumeConnector }{ {"NoExist", args{context.Background(), FCDriver}, nil}, {"Existed", args{context.Background(), ISCSIDriver}, testConnector}, diff --git a/connector/connector_utils.go b/connector/connector_utils.go index 5420520b..b962c35f 100644 --- a/connector/connector_utils.go +++ b/connector/connector_utils.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import ( "time" "huawei-csi-driver/csi/app" + "huawei-csi-driver/pkg/constants" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) @@ -50,6 +51,12 @@ const ( maxListTries = 10 // Location of the mount file to use procMountsPath = "/proc/mounts" + + extendDMBlockWaitTime = 2 * time.Second + watchDeviceInterval = 100 * time.Millisecond + volumeRemovalRetryTimes = 30 + + devLineSplitSegment = 2 ) var ( @@ -93,7 +100,7 @@ func getDevice(findDeviceMap map[string]string, deviceLink string) string { devLines := strings.Split(deviceLink, "\n") for _, line := range devLines { splits := strings.Split(line, "../../") - if len(splits) >= 2 { + if len(splits) >= devLineSplitSegment { name := splits[1] if strings.HasPrefix(name, "dm") { @@ -121,7 +128,7 @@ func getDevices(deviceLink string) []string { devLines := strings.Split(deviceLink, "\n") for _, line := range devLines { splits := strings.Split(line, "../../") - if len(splits) < 2 || utils.IsContain(splits[1], devices) { + if len(splits) < devLineSplitSegment || utils.IsContain(splits[1], devices) { continue } @@ -371,7 +378,7 @@ func removeSCSIDevice(ctx context.Context, sd string) error { func waitVolumeRemoval(ctx context.Context, devPaths []string) { existPath := devPaths - for index := 0; index <= 30; index++ { + for index := 0; index <= volumeRemovalRetryTimes; index++ { var exist []string for _, dev := range existPath { _, err := os.Stat(dev) @@ -387,7 +394,7 @@ func waitVolumeRemoval(ctx context.Context, devPaths []string) { return } - if index < 30 { + if index < volumeRemovalRetryTimes { time.Sleep(time.Second) } } @@ -458,7 +465,7 @@ func WatchDMDevice(ctx context.Context, lunWWN string, expectPathNumber int) (DM case <-timeout: return dm, err default: - time.Sleep(100 * time.Millisecond) + time.Sleep(watchDeviceInterval) } dm, err = findDMDeviceByWWN(ctx, lunWWN) @@ -767,7 +774,7 @@ func ResizeBlock(ctx context.Context, tgtLunWWN string, requiredBytes int64) err return utils.WaitUntil(func() (bool, error) { curSize := showDeviceSize(ctx, virtualDevice) - if curSize != "" && strconv.FormatInt(requiredBytes, 10) == curSize { + if curSize != "" && strconv.FormatInt(requiredBytes, constants.DefaultIntBase) == curSize { return true, nil } return false, nil @@ -1028,7 +1035,7 @@ func extendDMBlock(ctx context.Context, device string) error { } log.AddContext(ctx).Infof("Original size of block %s is %s", device, oldSize) - time.Sleep(time.Second * 2) + time.Sleep(extendDMBlockWaitTime) result, err := multiPathResizeMap(ctx, device) if err != nil || strings.Contains(result, "fail") { msg := fmt.Sprintf("Resize device %s err, output: %s, err: %v", device, result, err) @@ -1145,9 +1152,10 @@ func findMultiPathWWN(ctx context.Context, mPath string) (string, error) { return "", err } + const pathMapsLen = 3 for _, out := range strings.Split(output, "\n") { pathMaps := strings.Fields(out) - if len(pathMaps) == 3 && pathMaps[1] == mPath { + if len(pathMaps) == pathMapsLen && pathMaps[1] == mPath { return pathMaps[2], nil } } @@ -1412,7 +1420,7 @@ var GetDeviceSize = func(ctx context.Context, hostDevice string) (int64, error) if line == "" { continue } - size, err := strconv.ParseInt(line, 10, 64) + size, err := strconv.ParseInt(line, constants.DefaultIntBase, constants.DefaultIntBitSize) if err != nil { log.AddContext(ctx).Errorf("Failed to get device size %s, err is %v", line, err) return 0, err @@ -1576,7 +1584,7 @@ var RemoveAllDevice = func(ctx context.Context, } // ClearResidualPath used to clear residual path -func ClearResidualPath(ctx context.Context, lunWWN string, volumeMode interface{}) error { +func ClearResidualPath(ctx context.Context, lunWWN string, volumeMode any, multiPathType string) error { log.AddContext(ctx).Infof("Enter func: ClearResidualPath. lunWWN:[%s]. volumeMode:[%v]", lunWWN, volumeMode) v, ok := volumeMode.(string) @@ -1585,6 +1593,10 @@ func ClearResidualPath(ctx context.Context, lunWWN string, volumeMode interface{ return nil } + if err := clearUltraPathResidualPathByWwn(ctx, multiPathType, lunWWN); err != nil { + return err + } + devInfos, err := getDevicesInfosByGUID(ctx, lunWWN) if err != nil { return err @@ -1598,6 +1610,25 @@ func ClearResidualPath(ctx context.Context, lunWWN string, volumeMode interface{ return clearResidualPath(ctx, devInfos) } +func clearUltraPathResidualPathByWwn(ctx context.Context, multiPathType, lunWWN string) error { + if multiPathType != HWUltraPath { + return nil + } + + log.AddContext(ctx).Infoln("start to clear ultrapath specific residual paths by device WWN.") + vLun, err := GetUltrapathVLunByWWN(ctx, UltraPathCommand, lunWWN) + if err != nil { + return err + } + + if vLun == nil { + log.AddContext(ctx).Infoln("no ultrapath specific residual path to clear.") + return nil + } + + return vLun.CleanResidualPath(ctx) +} + func isPartitionDevice(ctx context.Context, dev string) (bool, error) { if strings.HasPrefix(dev, "dm") { // dm-* should convert to mpath* to determine whether it is a partition disk. @@ -1873,11 +1904,12 @@ func ReadMountPoints(ctx context.Context) (map[string]string, error) { return nil, err } + const splitLength = 2 mountMap := make(map[string]string) for _, line := range strings.Split(string(data), "\n") { if strings.TrimSpace(line) != "" { splitValue := strings.Split(line, " ") - if len(splitValue) >= 2 && splitValue[0] != "#" { + if len(splitValue) >= splitLength && splitValue[0] != "#" { mountMap[splitValue[1]] = splitValue[0] } } diff --git a/connector/fibrechannel/fc.go b/connector/fibrechannel/fc.go index 6799fdea..0438a511 100644 --- a/connector/fibrechannel/fc.go +++ b/connector/fibrechannel/fc.go @@ -24,19 +24,20 @@ import ( "huawei-csi-driver/utils/log" ) -// FibreChannel implements the Connector interface for FC protocol -type FibreChannel struct { +// Connector implements the connector.VolumeConnector for FC protocol +type Connector struct { } func init() { - connector.RegisterConnector(connector.FCDriver, &FibreChannel{}) + connector.RegisterConnector(connector.FCDriver, &Connector{}) } // ConnectVolume to mount the source to target path, the source path can be block or nfs // Example: -// mount /dev/sdb / -// mount / -func (fc *FibreChannel) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { +// +// mount /dev/sdb / +// mount / +func (fc *Connector) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { log.AddContext(ctx).Infof("FC Start to connect volume ==> connect info: %v", conn) tgtLunWWN, exist := conn["tgtLunWWN"].(string) if !exist { @@ -46,7 +47,7 @@ func (fc *FibreChannel) ConnectVolume(ctx context.Context, conn map[string]inter } // DisConnectVolume to unmount the target path -func (fc *FibreChannel) DisConnectVolume(ctx context.Context, tgtLunWWN string) error { +func (fc *Connector) DisConnectVolume(ctx context.Context, tgtLunWWN string) error { log.AddContext(ctx).Infof("FC Start to disconnect volume ==> volume wwn is: %v", tgtLunWWN) return connector.DisConnectVolumeCommon(ctx, tgtLunWWN, connector.FCDriver, tryDisConnectVolume) } diff --git a/connector/fibrechannel/fc_helper.go b/connector/fibrechannel/fc_helper.go index 162c9f8c..361d9043 100644 --- a/connector/fibrechannel/fc_helper.go +++ b/connector/fibrechannel/fc_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,18 @@ import ( "huawei-csi-driver/utils/log" ) +const ( + channelTargetLunLength = 2 + hostDeviceLength = 4 + waitDeviceDiscoveryTimeout = 60 * time.Second + waitDeviceDiscoveryInterval = 2 * time.Second + lunIdHighBits = 16 + lunIdAndOperationBits = 0xffff + wholeLunIdLength = 256 + devPathLength = 3 + attrWwnIndex = 2 +) + type target struct { tgtWWN string tgtHostLun string @@ -106,7 +118,11 @@ func constructFCInfo(conn *connectorInfo) { if index >= len(conn.tgtHostLUNs) { continue } - conn.tgtTargets = append(conn.tgtTargets, target{conn.tgtWWNs[index], conn.tgtHostLUNs[index]}) + tgt := target{ + tgtWWN: conn.tgtWWNs[index], + tgtHostLun: conn.tgtHostLUNs[index], + } + conn.tgtTargets = append(conn.tgtTargets, tgt) } } @@ -134,7 +150,7 @@ func tryConnectVolume(ctx context.Context, connMap map[string]interface{}) (stri } if devInfo.realDeviceName == "" { - log.AddContext(ctx).Warningln("No FibreChannel volume device found") + log.AddContext(ctx).Warningln("No Connector volume device found") return "", errors.New(connector.VolumeNotFound) } @@ -168,7 +184,8 @@ func checkPathAvailable(ctx context.Context, conn connectorInfo, devInfo deviceI switch conn.multiPathType { case connector.DMMultiPath: - return connector.VerifyDeviceAvailableOfDM(ctx, conn.tgtLunWWN, conn.pathCount, []string{devInfo.realDeviceName}, tryDisConnectVolume) + return connector.VerifyDeviceAvailableOfDM(ctx, + conn.tgtLunWWN, conn.pathCount, []string{devInfo.realDeviceName}, tryDisConnectVolume) case connector.HWUltraPath: return connector.GetDiskPathAndCheckStatus(ctx, connector.UltraPathCommand, conn.tgtLunWWN) case connector.HWUltraPathNVMe: @@ -210,7 +227,7 @@ func getHostAttrName(ctx context.Context, host, portAttr string) (string, error) if !strings.HasPrefix(line, "0x") { continue } - attrWwn := line[2:] + attrWwn := line[attrWwnIndex:] return attrWwn, nil } @@ -350,7 +367,12 @@ func getPossibleDeices(hbas []map[string]string, targets []target) []rawDevice { if pciNum != "" { for _, target := range targets { targetWWN := fmt.Sprintf("0x%s", strings.ToLower(target.tgtWWN)) - rawDev := rawDevice{platform, pciNum, targetWWN, target.tgtHostLun} + rawDev := rawDevice{ + platform: platform, + pciNum: pciNum, + wwn: targetWWN, + lun: target.tgtHostLun, + } rawDevices = append(rawDevices, rawDev) } } @@ -361,7 +383,7 @@ func getPossibleDeices(hbas []map[string]string, targets []target) []rawDevice { func getPci(devPath []string) (string, string) { var platform string - if len(devPath) <= 3 { + if len(devPath) <= devPathLength { return "", "" } platformSupport := devPath[3] == "platform" @@ -398,10 +420,11 @@ func formatLunId(lunId string) string { log.Warningf("formatLunId failed, lunId: %v, err: %v", lunId, err) } - if intLunId < 256 { + if intLunId < wholeLunIdLength { return lunId } else { - return fmt.Sprintf("0x%04x%04x00000000", intLunId&0xffff, intLunId>>16&0xffff) + return fmt.Sprintf("0x%04x%04x00000000", + intLunId&lunIdAndOperationBits, intLunId>>lunIdHighBits&lunIdAndOperationBits) } } @@ -415,7 +438,8 @@ func getHostDevices(ctx context.Context, possibleDevices []rawDevice) []string { platform = "" } - hostDevice := fmt.Sprintf("/dev/disk/by-path/%spci-%s-fc-%s-lun-%s", platform, value.pciNum, value.wwn, formatLunId(value.lun)) + hostDevice := fmt.Sprintf("/dev/disk/by-path/%spci-%s-fc-%s-lun-%s", + platform, value.pciNum, value.wwn, formatLunId(value.lun)) hostDevices = append(hostDevices, hostDevice) } log.AddContext(ctx).Infof("Get host devices are %v", hostDevices) @@ -456,14 +480,14 @@ func waitDeviceDiscovery(ctx context.Context, info.tries += 1 return false, nil - }, time.Second*60, time.Second*2) + }, waitDeviceDiscoveryTimeout, waitDeviceDiscoveryInterval) return info, err } func getHBAChannelSCSITargetLun(ctx context.Context, hba map[string]string, targets []target) ([][]string, []string) { hostDevice := hba["host_device"] - if hostDevice != "" && len(hostDevice) > 4 { - hostDevice = hostDevice[4:] + if hostDevice != "" && len(hostDevice) > hostDeviceLength { + hostDevice = hostDevice[hostDeviceLength:] } path := fmt.Sprintf("/sys/class/fc_transport/target%s:", hostDevice) @@ -483,10 +507,10 @@ func getHBAChannelSCSITargetLun(ctx context.Context, hba map[string]string, targ for _, line := range lines { if strings.HasPrefix(line, path) { lineList := strings.Split(line, "/") - if len(lineList) <= 4 { + if len(lineList) <= hostDeviceLength { continue } - ctlStr := lineList[4] + ctlStr := lineList[hostDeviceLength] ctlList := strings.Split(ctlStr, ":") if len(ctlList) <= 1 { continue @@ -503,8 +527,7 @@ func getHBAChannelSCSITargetLun(ctx context.Context, hba map[string]string, targ } func rescanHosts(ctx context.Context, hbas []map[string]string, conn *connectorInfo) { - var process []interface{} - var skipped []interface{} + var process, skipped []interface{} for _, hba := range hbas { ctls, lunWildCards := getHBAChannelSCSITargetLun(ctx, hba, conn.tgtTargets) if ctls != nil { @@ -522,10 +545,6 @@ func rescanHosts(ctx context.Context, hbas []map[string]string, conn *connectorI process = skipped } - var pathCount int - defer func() { - conn.pathCount = pathCount - }() for _, p := range process { pro, ok := p.([]interface{}) if !ok { @@ -552,7 +571,7 @@ func rescanHosts(ctx context.Context, hbas []map[string]string, conn *connectorI for _, c := range ctls { scanFC(ctx, c, hba["host_device"]) - pathCount++ + conn.pathCount++ if !conn.volumeUseMultiPath { break } @@ -564,7 +583,7 @@ func rescanHosts(ctx context.Context, hbas []map[string]string, conn *connectorI } func scanFC(ctx context.Context, channelTargetLun []string, hostDevice string) { - if len(channelTargetLun) <= 2 { + if len(channelTargetLun) <= channelTargetLunLength { return } scanCommand := fmt.Sprintf("echo \"%s %s %s\" > /sys/class/scsi_host/%s/scan", diff --git a/connector/host/host_helper_test.go b/connector/host/host_helper_test.go index cbb75f5f..72eb5008 100644 --- a/connector/host/host_helper_test.go +++ b/connector/host/host_helper_test.go @@ -25,7 +25,7 @@ import ( "github.com/agiledragon/gomonkey/v2" "github.com/prashantv/gostub" - "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" apiErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -84,23 +84,23 @@ func TestNewNodeHostInfo(t *testing.T) { }) defer getRoCEInitiator.Reset() - convey.Convey("TestNewNodeHostInfoSuccessful", t, func() { + t.Run("TestNewNodeHostInfoSuccessful", func(t *testing.T) { execShellCmd := gostub.StubFunc(&utils.ExecShellCmd, want.HostName, nil) defer execShellCmd.Reset() nodeHostInfo, err := NewNodeHostInfo(context.Background()) if !reflect.DeepEqual(nodeHostInfo, want) { t.Errorf("NewNodeHostInfo() got = %v, want %v", nodeHostInfo, want) } - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) - convey.Convey("TestNewNodeHostInfoWithQueryHostNameFail", t, func() { + t.Run("TestNewNodeHostInfoWithQueryHostNameFail", func(t *testing.T) { execShellCmd := gostub.StubFunc(&utils.ExecShellCmd, nil, errors.New("timeout")) defer execShellCmd.Reset() nodeHostInfo, err := NewNodeHostInfo(context.Background()) - convey.So(nodeHostInfo, convey.ShouldBeNil) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) + require.Nil(t, nodeHostInfo) }) } @@ -111,9 +111,9 @@ func TestSaveNodeHostInfoToSecretWithGetSecretError(t *testing.T) { }) defer getSecret.Reset() - convey.Convey("TestSaveNodeHostInfoToSecretWithGetSecretError", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithGetSecretError", func(t *testing.T) { err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) isNotFound := gomonkey.ApplyFunc(apiErrors.IsNotFound, func(err error) bool { @@ -121,7 +121,7 @@ func TestSaveNodeHostInfoToSecretWithGetSecretError(t *testing.T) { }) defer isNotFound.Reset() - convey.Convey("TestSaveNodeHostInfoToSecretWithIsNotFoundError", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithIsNotFoundError", func(t *testing.T) { createSecretFunc := func(_ *k8sutils.KubeClient, ctx context.Context, secret *corev1.Secret) (*corev1.Secret, error) { return secret, nil @@ -134,7 +134,7 @@ func TestSaveNodeHostInfoToSecretWithGetSecretError(t *testing.T) { defer newNodeHostInfo.Reset() err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) createSecret := gomonkey.ApplyMethod(reflect.TypeOf(testK8sUtils), "CreateSecret", @@ -148,19 +148,19 @@ func TestSaveNodeHostInfoToSecretWithGetSecretError(t *testing.T) { }) defer isAlreadyExists.Reset() - convey.Convey("TestSaveNodeHostInfoToSecretWithSecretNotExistAndCreateFail", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithSecretNotExistAndCreateFail", func(t *testing.T) { err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) - convey.Convey("TestSaveNodeHostInfoToSecretWithSecretNotExistAndCreateReturnExists", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithSecretNotExistAndCreateReturnExists", func(t *testing.T) { err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestSaveNodeHostInfoToSecretWithSecretNotExist(t *testing.T) { - convey.Convey("TestSaveNodeHostInfoToSecretWithGetSecretNotExist", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithGetSecretNotExist", func(t *testing.T) { getSecret := gomonkey.ApplyMethod(reflect.TypeOf(testK8sUtils), "GetSecret", func(_ *k8sutils.KubeClient, ctx context.Context, secretName, namespace string) (*corev1.Secret, error) { return &corev1.Secret{}, nil @@ -190,12 +190,12 @@ func TestSaveNodeHostInfoToSecretWithSecretNotExist(t *testing.T) { defer updateSecret.Reset() err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) } func TestSaveNodeHostInfoToSecretWithNewHostInfoError(t *testing.T) { - convey.Convey("TestSaveNodeHostInfoToSecretWithNewHostInfoError", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithNewHostInfoError", func(t *testing.T) { getSecret := gomonkey.ApplyMethod(reflect.TypeOf(testK8sUtils), "GetSecret", func(_ *k8sutils.KubeClient, ctx context.Context, secretName, namespace string) (*corev1.Secret, error) { return &corev1.Secret{}, nil @@ -208,7 +208,7 @@ func TestSaveNodeHostInfoToSecretWithNewHostInfoError(t *testing.T) { defer newNodeHostInfo.Reset() err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } @@ -224,7 +224,7 @@ func TestSaveNodeHostInfoToSecretWithUpdateSecret(t *testing.T) { }) defer newNodeHostInfo.Reset() - convey.Convey("TestSaveNodeHostInfoToSecretWithMarshalError", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithMarshalError", func(t *testing.T) { errorMsg := "marshal error" marshal := gomonkey.ApplyFunc(json.Marshal, func(v any) ([]byte, error) { return nil, errors.New(errorMsg) @@ -232,11 +232,11 @@ func TestSaveNodeHostInfoToSecretWithUpdateSecret(t *testing.T) { defer marshal.Reset() err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeError) - convey.So(err, convey.ShouldEqual, errorMsg) + require.Error(t, err) + require.ErrorContains(t, err, errorMsg) }) - convey.Convey("TestSaveNodeHostInfoToSecretWithUpdateSecretFail", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithUpdateSecretFail", func(t *testing.T) { updateSecret := gomonkey.ApplyMethod(reflect.TypeOf(testK8sUtils), "UpdateSecret", func(_ *k8sutils.KubeClient, ctx context.Context, secret *corev1.Secret) (*corev1.Secret, error) { return nil, errors.New("error") @@ -244,10 +244,10 @@ func TestSaveNodeHostInfoToSecretWithUpdateSecret(t *testing.T) { defer updateSecret.Reset() err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) - convey.Convey("TestSaveNodeHostInfoToSecretWithUpdateSecretSuccess", t, func() { + t.Run("TestSaveNodeHostInfoToSecretWithUpdateSecretSuccess", func(t *testing.T) { updateSecret := gomonkey.ApplyMethod(reflect.TypeOf(testK8sUtils), "UpdateSecret", func(_ *k8sutils.KubeClient, ctx context.Context, secret *corev1.Secret) (*corev1.Secret, error) { return secret, nil @@ -255,12 +255,12 @@ func TestSaveNodeHostInfoToSecretWithUpdateSecret(t *testing.T) { defer updateSecret.Reset() err := SaveNodeHostInfoToSecret(context.Background()) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) } func TestGetNodeHostInfosFromSecret(t *testing.T) { - convey.Convey("TestGetNodeHostInfosFromSecretAndGetSecretError", t, func() { + t.Run("TestGetNodeHostInfosFromSecretAndGetSecretError", func(t *testing.T) { getSecret := gomonkey.ApplyMethod(reflect.TypeOf(testK8sUtils), "GetSecret", func(_ *k8sutils.KubeClient, ctx context.Context, secretName, namespace string) (*corev1.Secret, error) { return nil, errors.New(" get secret error") @@ -268,10 +268,10 @@ func TestGetNodeHostInfosFromSecret(t *testing.T) { defer getSecret.Reset() nodeHostInfos, err := GetNodeHostInfosFromSecret(context.Background(), testNodeInfo.HostName) - convey.So(err, convey.ShouldBeError) - convey.So(nodeHostInfos, convey.ShouldBeNil) + require.Error(t, err) + require.Nil(t, nodeHostInfos) }) - convey.Convey("TestGetNodeHostInfosFromSecretAndDataIsNil", t, func() { + t.Run("TestGetNodeHostInfosFromSecretAndDataIsNil", func(t *testing.T) { getSecret := gomonkey.ApplyMethod(reflect.TypeOf(testK8sUtils), "GetSecret", func(_ *k8sutils.KubeClient, ctx context.Context, secretName, namespace string) (*corev1.Secret, error) { return &corev1.Secret{}, nil @@ -279,11 +279,11 @@ func TestGetNodeHostInfosFromSecret(t *testing.T) { defer getSecret.Reset() nodeHostInfos, err := GetNodeHostInfosFromSecret(context.Background(), testNodeInfo.HostName) - convey.So(err, convey.ShouldBeError) - convey.So(err.Error(), convey.ShouldEqual, "secret data is empty") - convey.So(nodeHostInfos, convey.ShouldBeNil) + require.Error(t, err) + require.EqualError(t, err, "secret data is empty") + require.Nil(t, nodeHostInfos) }) - convey.Convey("TestGetNodeHostInfosFromSecretSuccess", t, func() { + t.Run("TestGetNodeHostInfosFromSecretSuccess", func(t *testing.T) { secretData, err := json.Marshal(testNodeInfo) if err != nil { t.Errorf("TestGetNodeHostInfosFromSecretSuccess() json marshal %v", err) @@ -301,7 +301,7 @@ func TestGetNodeHostInfosFromSecret(t *testing.T) { defer getSecret.Reset() testReturnData, err := GetNodeHostInfosFromSecret(context.Background(), testNodeInfo.HostName) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) if !reflect.DeepEqual(testReturnData, testNodeInfo) { t.Errorf("TestGetNodeHostInfosFromSecretSuccess() got = %v, want %v", testReturnData, testNodeInfo) } diff --git a/connector/iscsi/iscsi.go b/connector/iscsi/iscsi.go index 9aaf03c4..2482b9f5 100644 --- a/connector/iscsi/iscsi.go +++ b/connector/iscsi/iscsi.go @@ -24,12 +24,12 @@ import ( "huawei-csi-driver/utils/log" ) -// ISCSI implements the Connector interface for ISCSI protocol -type ISCSI struct { +// Connector implements the connector.VolumeConnector for Connector protocol +type Connector struct { } func init() { - connector.RegisterConnector(connector.ISCSIDriver, &ISCSI{}) + connector.RegisterConnector(connector.ISCSIDriver, &Connector{}) } // ConnectVolume to mount the source to target path, the source path can be block or nfs @@ -37,7 +37,7 @@ func init() { // // mount /dev/sdb / // mount / -func (isc *ISCSI) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { +func (isc *Connector) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { log.AddContext(ctx).Infof("ISCSI Start to connect volume ==> connect info: %v", utils.MaskConnSensitiveInfo(conn)) tgtLunWWN, exist := conn["tgtLunWWN"].(string) @@ -48,7 +48,7 @@ func (isc *ISCSI) ConnectVolume(ctx context.Context, conn map[string]interface{} } // DisConnectVolume to unmount the target path -func (isc *ISCSI) DisConnectVolume(ctx context.Context, tgtLunWWN string) error { +func (isc *Connector) DisConnectVolume(ctx context.Context, tgtLunWWN string) error { log.AddContext(ctx).Infof("ISCSI Start to disconnect volume ==> volume wwn is: %v", tgtLunWWN) return connector.DisConnectVolumeCommon(ctx, tgtLunWWN, connector.ISCSIDriver, tryDisConnectVolume) } diff --git a/connector/iscsi/iscsi_helper.go b/connector/iscsi/iscsi_helper.go index 20fd0baf..eb0b4a63 100644 --- a/connector/iscsi/iscsi_helper.go +++ b/connector/iscsi/iscsi_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,14 @@ import ( "context" "errors" "fmt" - "io/ioutil" "math" + "os" "path/filepath" "runtime/debug" "sort" "strings" "sync" + "sync/atomic" "time" "huawei-csi-driver/connector" @@ -37,6 +38,24 @@ import ( "huawei-csi-driver/utils/log" ) +const ( + timeForDMAppear = 15 + wwnTypeIndex = 4 + wwidIndex = 6 + scanSingleTimes = 15 + scanExponent = 2.0 + scanStep = 2 + hostChannelTargetLunLength = 4 + splitPathSegment = 2 + hostIndexOfTargetPath = 26 + splitInfoSegment = 4 + targetInfoSegment = 2 + + scanSingleInterval = 2 * time.Second + iscsiSessionLoginInterval = 2 * time.Second + iscsiSessionLoginTimes = 60 +) + var singleGroup = concurrent.NewSingleGroup[connectResult]() type connectResult struct { @@ -68,12 +87,12 @@ type singleConnectorInfo struct { } type shareData struct { - stopConnecting bool - numLogin int64 - failedLogin int64 - stoppedThreads int64 - foundDevices []string - justAddedDevices []string + stopConnecting atomic.Bool + numLogin atomic.Int64 + failedLogin atomic.Int64 + stoppedThreads atomic.Int64 + foundDevices concurrent.Slice[string] + justAddedDevices concurrent.Slice[string] } type scanRequest struct { @@ -184,7 +203,7 @@ func updateISCSIAdminWithExitCode(ctx context.Context, return runISCSIAdmin(ctx, tgtPortal, targetIQN, iscsiCMD, checkExitCode) } -func iscsiCMD(propertyKey, propertyValue string) string { +func genIscsiOpArgs(propertyKey, propertyValue string) string { return fmt.Sprintf("--op update -n %s -v %s", propertyKey, propertyValue) } @@ -236,14 +255,14 @@ func getAllISCSISession(ctx context.Context) [][]string { for _, iscsi := range strings.Split(allSessions, "\n") { if iscsi != "" { splitInfo := strings.Split(iscsi, " ") - if len(splitInfo) < 4 { + if len(splitInfo) < splitInfoSegment { log.AddContext(ctx).Warningf("iscsi session %s error", splitInfo) continue } sid := splitInfo[1][1 : len(splitInfo[1])-1] tgtInfo := strings.Split(splitInfo[2], ",") - if len(tgtInfo) < 2 { + if len(tgtInfo) < targetInfoSegment { continue } portal, tpgt := tgtInfo[0], tgtInfo[1] @@ -289,8 +308,7 @@ func connectISCSIPortal(ctx context.Context, var manualScan bool err = updateISCSIAdmin(ctx, tgtPortal, targetIQN, "node.session.scan", "manual") if err != nil { - log.AddContext(ctx).Warningf("Update node session scan mode to manual error, reason: %v", - tgtPortal, err) + log.AddContext(ctx).Warningf("Update node session scan mode to manual error, reason: %v", err) } manualScan = err == nil @@ -301,7 +319,7 @@ func connectISCSIPortal(ctx context.Context, return "", false } - for i := 0; i < 60; i++ { + for i := 0; i < iscsiSessionLoginTimes; i++ { sessions := getAllISCSISession(ctx) for _, s := range sessions { if s[0] == "tcp:" && strings.ToLower(tgtPortal) == strings.ToLower(s[2]) && targetIQN == s[4] { @@ -323,7 +341,7 @@ func connectISCSIPortal(ctx context.Context, return "", false } - time.Sleep(time.Second * 2) + time.Sleep(iscsiSessionLoginInterval) } return "", false } @@ -340,7 +358,7 @@ func getHostChannelTargetLun(session, tgtLun string) []string { if paths != nil { _, file := filepath.Split(paths[0]) splitPath := strings.Split(file, ":") - if len(splitPath) <= 2 { + if len(splitPath) <= splitPathSegment { return nil } channel = splitPath[1] @@ -354,14 +372,14 @@ func getHostChannelTargetLun(session, tgtLun string) []string { } } - index := strings.Index(paths[0][26:], "/") - host = paths[0][26:][:index] + index := strings.Index(paths[0][hostIndexOfTargetPath:], "/") + host = paths[0][hostIndexOfTargetPath:][:index] hostChannelTargetLun = append(hostChannelTargetLun, host, channel, target, tgtLun) return hostChannelTargetLun } func scanISCSI(ctx context.Context, hostChannelTargetLun []string) { - if len(hostChannelTargetLun) <= 3 { + if len(hostChannelTargetLun) < hostChannelTargetLunLength { return } channelTargetLun := fmt.Sprintf("%s %s %s", hostChannelTargetLun[1], hostChannelTargetLun[2], @@ -419,7 +437,7 @@ func (s *deviceScan) scan(ctx context.Context, if s.secondNextScan <= 0 { s.numRescans++ scanISCSI(ctx, req.hostChannelTargetLun) - s.secondNextScan = int(math.Pow(float64(s.numRescans+2), 2.0)) + s.secondNextScan = int(math.Pow(float64(s.numRescans+scanStep), scanExponent)) } device = getDeviceByHCTL(req.sessionId, req.hostChannelTargetLun) @@ -429,7 +447,8 @@ func (s *deviceScan) scan(ctx context.Context, device = connector.ClearUnavailableDevice(ctx, device, req.tgtLunWWN) } - doScans = s.numRescans <= deviceScanAttemptsDefault && !(device != "" || req.iSCSIShareData.stopConnecting) + doScans = s.numRescans <= deviceScanAttemptsDefault && + !(device != "" || req.iSCSIShareData.stopConnecting.Load()) if doScans { time.Sleep(time.Second) s.secondNextScan-- @@ -456,7 +475,7 @@ func connectVol(ctx context.Context, secondNextScan = 4 } - iSCSIShareData.numLogin += 1 + iSCSIShareData.numLogin.Add(1) dScan := deviceScan{ numRescans: numRescans, secondNextScan: secondNextScan, @@ -469,15 +488,15 @@ func connectVol(ctx context.Context, log.AddContext(ctx).Debugf("LUN %s on iSCSI portal %s not found on sysfs after logging in.", tgt.tgtHostLun, tgt.tgtPortal) } else { - iSCSIShareData.foundDevices = append(iSCSIShareData.foundDevices, device) - iSCSIShareData.justAddedDevices = append(iSCSIShareData.justAddedDevices, device) + iSCSIShareData.foundDevices.Append(device) + iSCSIShareData.justAddedDevices.Append(device) } } else { log.AddContext(ctx).Warningf("build iSCSI session %s error", tgt.tgtPortal) - iSCSIShareData.failedLogin += 1 + iSCSIShareData.failedLogin.Add(1) } - iSCSIShareData.stoppedThreads += 1 + iSCSIShareData.stoppedThreads.Add(1) return } @@ -524,10 +543,10 @@ func tryConnectVolume(ctx context.Context, connMap map[string]interface{}) (stri if err != nil { log.AddContext(ctx).Errorf("failed to find a disk. %v", err) } - iSCSIShareData.stopConnecting = true + iSCSIShareData.stopConnecting.Store(true) wait.Wait() - return checkDeviceAvailable(ctx, conn, iSCSIShareData, diskName, int(iSCSIShareData.numLogin)) + return checkDeviceAvailable(ctx, conn, iSCSIShareData, diskName, int(iSCSIShareData.numLogin.Load())) } func catchConnectError(ctx context.Context) { @@ -588,10 +607,10 @@ func checkDeviceAvailable(ctx context.Context, } if diskName == "" { - err := connector.RemoveDevices(ctx, iSCSIShareData.foundDevices) + err := connector.RemoveDevices(ctx, iSCSIShareData.foundDevices.Values()) if err != nil { log.AddContext(ctx).Warningf("Remove devices %v error: %v", - iSCSIShareData.foundDevices, err) + iSCSIShareData.foundDevices.Values(), err) } return "", utils.Errorln(ctx, connector.VolumeNotFound) } @@ -599,7 +618,7 @@ func checkDeviceAvailable(ctx context.Context, switch conn.multiPathType { case connector.DMMultiPath: return connector.VerifyDeviceAvailableOfDM(ctx, conn.tgtLunWWN, - expectPathNumber, iSCSIShareData.foundDevices, tryDisConnectVolume) + expectPathNumber, iSCSIShareData.foundDevices.Values(), tryDisConnectVolume) case connector.HWUltraPath: return connector.VerifyDeviceAvailableOfUltraPath(ctx, connector.UltraPathCommand, diskName) case connector.HWUltraPathNVMe: @@ -610,11 +629,11 @@ func checkDeviceAvailable(ctx context.Context, } func checkSinglePathAvailable(ctx context.Context, iSCSIShareData *shareData, tgtLunWWN string) (string, error) { - if len(iSCSIShareData.foundDevices) == 0 { + if iSCSIShareData.foundDevices.Len() == 0 { return "", errors.New(connector.VolumeNotFound) } - device := fmt.Sprintf("/dev/%s", iSCSIShareData.foundDevices[0]) + device := fmt.Sprintf("/dev/%s", iSCSIShareData.foundDevices.Get(0)) err := connector.VerifySingleDevice(ctx, device, tgtLunWWN, connector.VolumeNotFound, tryDisConnectVolume) if err != nil { @@ -624,25 +643,25 @@ func checkSinglePathAvailable(ctx context.Context, iSCSIShareData *shareData, tg } func scanSingle(iSCSIShareData *shareData) { - for i := 0; i < 15; i++ { - if len(iSCSIShareData.foundDevices) != 0 { + for i := 0; i < scanSingleTimes; i++ { + if iSCSIShareData.foundDevices.Len() != 0 { break } - time.Sleep(time.Second * 2) + time.Sleep(scanSingleInterval) } } func getSYSfsWwn(ctx context.Context, foundDevices []string, mPath string) (string, error) { if mPath != "" { dmFile := fmt.Sprintf("/sys/block/%s/dm/uuid", mPath) - data, err := ioutil.ReadFile(dmFile) + data, err := os.ReadFile(dmFile) if err != nil { msg := fmt.Sprintf("Read dm file %s error: %v", dmFile, err) log.AddContext(ctx).Errorln(msg) return "", errors.New(msg) } - if wwid := data[6:]; wwid != nil { + if wwid := data[wwidIndex:]; wwid != nil { return string(wwid), nil } } @@ -652,19 +671,19 @@ func getSYSfsWwn(ctx context.Context, foundDevices []string, mPath string) (stri } for _, device := range foundDevices { deviceFile := fmt.Sprintf("/sys/block/%s/device/wwid", device) - data, err := ioutil.ReadFile(deviceFile) + data, err := os.ReadFile(deviceFile) if err != nil { msg := fmt.Sprintf("Read device file %s error: %v", deviceFile, err) log.AddContext(ctx).Errorln(msg) continue } - wwnType, exist := wwnTypes[string(data[:4])] + wwnType, exist := wwnTypes[string(data[:wwnTypeIndex])] if !exist { wwnType = "8" } - wwid := wwnType + string(data[4:]) + wwid := wwnType + string(data[wwnTypeIndex:]) return wwid, nil } @@ -703,19 +722,21 @@ func addMultiPath(ctx context.Context, devPath string) error { } func tryScanMultiDevice(ctx context.Context, mPath string, iSCSIShareData *shareData) string { - for mPath == "" && len(iSCSIShareData.justAddedDevices) != 0 { - devicePath := "/dev/" + iSCSIShareData.justAddedDevices[0] - iSCSIShareData.justAddedDevices = iSCSIShareData.justAddedDevices[1:] + for mPath == "" && iSCSIShareData.justAddedDevices.Len() != 0 { + devicePath := "/dev/" + iSCSIShareData.justAddedDevices.Get(0) + if err := iSCSIShareData.justAddedDevices.Cut(1, iSCSIShareData.justAddedDevices.Len()); err != nil { + log.AddContext(ctx).Warningln(err) + } err := addMultiPath(ctx, devicePath) if err != nil { log.AddContext(ctx).Warningf("Add multiPath path failed, error: %s", err) } var isClear bool - mPath, isClear = connector.FindAvailableMultiPath(ctx, iSCSIShareData.foundDevices) + mPath, isClear = connector.FindAvailableMultiPath(ctx, iSCSIShareData.foundDevices.Values()) if isClear { - iSCSIShareData.foundDevices = nil - iSCSIShareData.justAddedDevices = nil + iSCSIShareData.foundDevices.Reset() + iSCSIShareData.justAddedDevices.Reset() } } return mPath @@ -726,12 +747,12 @@ func scanMultiDevice(ctx context.Context, iSCSIShareData *shareData, wwnAdded bool) (string, bool) { var err error - if mPath == "" && len(iSCSIShareData.foundDevices) != 0 { + if mPath == "" && iSCSIShareData.foundDevices.Len() != 0 { var isClear bool - mPath, isClear = connector.FindAvailableMultiPath(ctx, iSCSIShareData.foundDevices) + mPath, isClear = connector.FindAvailableMultiPath(ctx, iSCSIShareData.foundDevices.Values()) if isClear { - iSCSIShareData.foundDevices = nil - iSCSIShareData.justAddedDevices = nil + iSCSIShareData.foundDevices.Reset() + iSCSIShareData.justAddedDevices.Reset() } if wwn != "" && !(mPath != "" || wwnAdded) { @@ -750,8 +771,8 @@ func scanMultiDevice(ctx context.Context, func findDiskOfUltraPath(ctx context.Context, lenIndex int, iSCSIShareData *shareData, upType, lunWWN string) string { var diskName string var err error - for !((int64(lenIndex) == iSCSIShareData.stoppedThreads && len(iSCSIShareData.foundDevices) == 0) || - (diskName != "" && int64(lenIndex) == iSCSIShareData.numLogin+iSCSIShareData.failedLogin)) { + for !((int64(lenIndex) == iSCSIShareData.stoppedThreads.Load() && iSCSIShareData.foundDevices.Len() == 0) || + (diskName != "" && int64(lenIndex) == iSCSIShareData.numLogin.Load()+iSCSIShareData.failedLogin.Load())) { diskName, err = connector.GetDiskNameByWWN(ctx, upType, lunWWN) if err == nil { @@ -768,10 +789,10 @@ func findDiskOfDM(ctx context.Context, lenIndex int, LunWWN string, iSCSIShareDa var lastTryOn int64 var mPath, wwn string var err error - for !((int64(lenIndex) == iSCSIShareData.stoppedThreads && len(iSCSIShareData.foundDevices) == 0) || - (mPath != "" && int64(lenIndex) == iSCSIShareData.numLogin+iSCSIShareData.failedLogin)) { - if wwn == "" && len(iSCSIShareData.foundDevices) != 0 { - wwn, err = getSYSfsWwn(ctx, iSCSIShareData.foundDevices, mPath) + for !((int64(lenIndex) == iSCSIShareData.stoppedThreads.Load() && iSCSIShareData.foundDevices.Len() == 0) || + (mPath != "" && int64(lenIndex) == iSCSIShareData.numLogin.Load()+iSCSIShareData.failedLogin.Load())) { + if wwn == "" && iSCSIShareData.foundDevices.Len() != 0 { + wwn, err = getSYSfsWwn(ctx, iSCSIShareData.foundDevices.Values(), mPath) if err != nil { break } @@ -782,10 +803,10 @@ func findDiskOfDM(ctx context.Context, lenIndex int, LunWWN string, iSCSIShareDa } mPath, wwnAdded = scanMultiDevice(ctx, mPath, wwn, iSCSIShareData, wwnAdded) - if lastTryOn == 0 && len(iSCSIShareData.foundDevices) != 0 && int64( - lenIndex) == iSCSIShareData.stoppedThreads { + if lastTryOn == 0 && iSCSIShareData.foundDevices.Len() != 0 && + int64(lenIndex) == iSCSIShareData.stoppedThreads.Load() { log.AddContext(ctx).Infoln("All connection threads finished, giving 15 seconds for dm to appear.") - lastTryOn = time.Now().Unix() + 15 + lastTryOn = time.Now().Unix() + timeForDMAppear } else if lastTryOn != 0 && lastTryOn < time.Now().Unix() { break } @@ -815,7 +836,7 @@ func getISCSISession(ctx context.Context, devSessionIds []string) []singleConnec func disconnectFromISCSIPortal(ctx context.Context, tgtPortal, targetIQN string) { checkExitCode := []string{"exit status 0", "exit status 15", "exit status 255"} err := updateISCSIAdminWithExitCode(ctx, tgtPortal, targetIQN, - iscsiCMD("node.startup", "manual"), + genIscsiOpArgs("node.startup", "manual"), checkExitCode) if err != nil { log.AddContext(ctx).Warningf("Update node startUp error, reason: %v", err) diff --git a/connector/local/local.go b/connector/local/local.go index 7d90f30a..0dd45060 100644 --- a/connector/local/local.go +++ b/connector/local/local.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,26 +19,23 @@ package local import ( "context" - "time" "huawei-csi-driver/connector" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) -// Local to define a local lock when connect or disconnect, in order to preventing connect and disconnect confusion -type Local struct { +// Connector to define a local lock when connect or disconnect, in order to preventing connect and disconnect confusion +type Connector struct { } -var waitDevOnlineTimeInterval = 2 * time.Second - func init() { - connector.RegisterConnector(connector.LocalDriver, &Local{}) + connector.RegisterConnector(connector.LocalDriver, &Connector{}) } // ConnectVolume to connect local volume, such as /dev/disk/by-id/wwn-0x* -func (loc *Local) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { - log.AddContext(ctx).Infof("Local Start to connect volume ==> connect info: %v", conn) +func (loc *Connector) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { + log.AddContext(ctx).Infof("Local connector Start to connect volume ==> connect info: %v", conn) tgtLunWWN, exist := conn["tgtLunWWN"].(string) if !exist { return "", utils.Errorln(ctx, "key tgtLunWWN does not exist in connection properties") @@ -47,7 +44,7 @@ func (loc *Local) ConnectVolume(ctx context.Context, conn map[string]interface{} } // DisConnectVolume to remove the local lun path -func (loc *Local) DisConnectVolume(ctx context.Context, tgtLunWWN string) error { - log.AddContext(ctx).Infof("Local Start to disconnect volume ==> volume wwn is: %v", tgtLunWWN) +func (loc *Connector) DisConnectVolume(ctx context.Context, tgtLunWWN string) error { + log.AddContext(ctx).Infof("Local Connector Start to disconnect volume ==> volume wwn is: %v", tgtLunWWN) return connector.DisConnectVolumeCommon(ctx, tgtLunWWN, connector.LocalDriver, tryDisConnectVolume) } diff --git a/connector/local/local_helper.go b/connector/local/local_helper.go index e4391d43..eb53f4a7 100644 --- a/connector/local/local_helper.go +++ b/connector/local/local_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ import ( "huawei-csi-driver/utils/log" ) +const waitDevOnlineTimeInterval = 2 * time.Second + func waitDevOnline(ctx context.Context, tgtLunWWN string) string { devPath := fmt.Sprintf("/dev/disk/by-id/wwn-0x%s", tgtLunWWN) for i := 0; i < 30; i++ { diff --git a/connector/local/local_test.go b/connector/local/local_test.go index 2c90094f..1c733cc6 100644 --- a/connector/local/local_test.go +++ b/connector/local/local_test.go @@ -77,7 +77,8 @@ func TestConnectVolume(t *testing.T) { }, } - stubs := gostub.Stub(&waitDevOnlineTimeInterval, time.Millisecond) + var interval = waitDevOnlineTimeInterval + stubs := gostub.Stub(&interval, time.Millisecond) defer stubs.Reset() stubs.Stub(&utils.ExecShellCmd, func(ctx context.Context, format string, args ...interface{}) (string, error) { @@ -90,7 +91,7 @@ func TestConnectVolume(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - loc := &Local{} + loc := &Connector{} got, err := loc.ConnectVolume(tt.args.ctx, tt.args.conn) if (err != nil) != tt.wantErr { t.Errorf("ConnectVolume() error = %v, wantErr %v", err, tt.wantErr) @@ -156,7 +157,7 @@ func TestDisConnectVolume(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - loc := &Local{} + loc := &Connector{} err := loc.DisConnectVolume(tt.args.ctx, tt.args.tgtLunWWN) if (err != nil) != tt.wantErr { t.Errorf("DisConnectVolume() error = %v, wantErr %v", err, tt.wantErr) diff --git a/connector/nfs/nfs.go b/connector/nfs/nfs.go index d915538d..3d052464 100644 --- a/connector/nfs/nfs.go +++ b/connector/nfs/nfs.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import ( "huawei-csi-driver/utils/log" ) -// NFS to define a local lock when connect or disconnect, in order to preventing mounting and unmounting confusion -type NFS struct { +// Connector to define a local lock when connect or disconnect, in order to preventing mounting and unmounting confusion +type Connector struct { } const ( @@ -38,20 +38,21 @@ const ( ) func init() { - connector.RegisterConnector(connector.NFSDriver, &NFS{}) + connector.RegisterConnector(connector.NFSDriver, &Connector{}) } // ConnectVolume to mount the source to target path, the source path can be block or nfs // Example: -// mount /dev/sdb / -// mount / -func (nfs *NFS) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { - log.AddContext(ctx).Infof("NFS Start to connect volume ==> connect info: %v", conn) +// +// mount /dev/sdb / +// mount / +func (nfs *Connector) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { + log.AddContext(ctx).Infof("Nfs Start to connect volume ==> connect info: %v", conn) return tryConnectVolume(ctx, conn) } // DisConnectVolume to unmount the target path -func (nfs *NFS) DisConnectVolume(ctx context.Context, targetPath string) error { +func (nfs *Connector) DisConnectVolume(ctx context.Context, targetPath string) error { log.AddContext(ctx).Infof("NFS Start to disconnect volume ==> target path is: %v", targetPath) return tryDisConnectVolume(ctx, targetPath) } diff --git a/connector/nfs/nfs_helper.go b/connector/nfs/nfs_helper.go index 559da9bb..4e73bd70 100644 --- a/connector/nfs/nfs_helper.go +++ b/connector/nfs/nfs_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,12 @@ import ( "huawei-csi-driver/utils/log" ) +const ( + fsInfoSegment = 2 + targetMountPathPermission = 0750 + unformattedFsCode = 2 +) + type connectorInfo struct { srcType string sourcePath string @@ -109,7 +115,7 @@ func tryConnectVolume(ctx context.Context, connMap map[string]interface{}) (stri return "", err } - err = mountDisk(ctx, conn.sourcePath, conn.targetPath, conn.fsType, conn.mntFlags, conn.accessMode) + err = mountDisk(ctx, conn) if err != nil { return "", err } @@ -132,7 +138,7 @@ func preMount(sourcePath, targetPath string, checkSourcePath bool) error { } if _, err := os.Stat(targetPath); err != nil && os.IsNotExist(err) { - if err := os.MkdirAll(targetPath, 0750); err != nil { + if err := os.MkdirAll(targetPath, targetMountPathPermission); err != nil { return errors.New("can not create a target path") } } @@ -278,7 +284,7 @@ func getFSType(ctx context.Context, sourcePath string) (string, error) { output, err := utils.ExecShellCmd(ctx, "blkid -o udev %s", sourcePath) if err != nil { - if errCode, ok := err.(*exec.ExitError); ok && errCode.ExitCode() == 2 { + if errCode, ok := err.(*exec.ExitError); ok && errCode.ExitCode() == unformattedFsCode { log.AddContext(ctx).Infof("Query fs of %s, output: %s, error: %s", sourcePath, output, err) if formatted, err := connector.IsDeviceFormatted(ctx, sourcePath); err != nil { return "", fmt.Errorf("check device %s formatted failed, error: %v", sourcePath, err) @@ -294,7 +300,7 @@ func getFSType(ctx context.Context, sourcePath string) (string, error) { for _, out := range strings.Split(output, "\n") { fsInfo := strings.Split(out, "=") - if len(fsInfo) == 2 && fsInfo[0] == "ID_FS_TYPE" { + if len(fsInfo) == fsInfoSegment && fsInfo[0] == "ID_FS_TYPE" { return fsInfo[1], nil } } @@ -361,60 +367,59 @@ func getDiskSizeType(ctx context.Context, sourcePath string) (string, error) { return "", errors.New("the disk size does not support") } -func mountDisk(ctx context.Context, sourcePath, targetPath, fsType string, flags mountParam, - accessMode csi.VolumeCapability_AccessMode_Mode) error { +func mountDisk(ctx context.Context, conn *connectorInfo) error { var err error - existFsType, err := getFSType(ctx, sourcePath) + existFsType, err := getFSType(ctx, conn.sourcePath) if err != nil { return err } if existFsType == "" { // check this disk is in formatting - inFormatting, err := connector.IsInFormatting(ctx, sourcePath, fsType) + inFormatting, err := connector.IsInFormatting(ctx, conn.sourcePath, conn.fsType) if err != nil { return err } if inFormatting { - log.AddContext(ctx).Infof("Device %s is in formatting, no need format again. Wait 10 seconds", sourcePath) + log.AddContext(ctx).Infof("Device %s is in formatting, no need format again. Wait 10 seconds", conn.sourcePath) time.Sleep(time.Second * formatWaitInternal) return errors.New("the disk is in formatting, please wait") } - diskSizeType, err := getDiskSizeType(ctx, sourcePath) + diskSizeType, err := getDiskSizeType(ctx, conn.sourcePath) if err != nil { return err } - err = formatDisk(ctx, sourcePath, fsType, diskSizeType) + err = formatDisk(ctx, conn.sourcePath, conn.fsType, diskSizeType) if err != nil { return err } - err = mountUnix(ctx, sourcePath, targetPath, flags, true) + err = mountUnix(ctx, conn.sourcePath, conn.targetPath, conn.mntFlags, true) if err != nil { return err } } else { - err = mountUnix(ctx, sourcePath, targetPath, flags, true) + err = mountUnix(ctx, conn.sourcePath, conn.targetPath, conn.mntFlags, true) if err != nil { return err } - if accessMode == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER { + if conn.accessMode == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER { log.AddContext(ctx).Infoln("PVC accessMode is ReadWriteMany, not support to expend filesystem") return nil } - if accessMode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY { + if conn.accessMode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY { log.AddContext(ctx).Infoln("PVC accessMode is ReadOnlyMany, no need to expend filesystem") return nil } - err = connector.ResizeMountPath(ctx, targetPath) + err = connector.ResizeMountPath(ctx, conn.targetPath) if err != nil { - log.AddContext(ctx).Errorf("Resize mount path %s err %s", targetPath, err) + log.AddContext(ctx).Errorf("Resize mount path %s err %s", conn.targetPath, err) return err } } diff --git a/connector/nfs/nfs_test.go b/connector/nfs/nfs_test.go index 05400198..edcd32ea 100644 --- a/connector/nfs/nfs_test.go +++ b/connector/nfs/nfs_test.go @@ -130,7 +130,7 @@ func TestConnectVolume(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - nfs := &NFS{} + nfs := &Connector{} got, err := nfs.ConnectVolume(tt.args.ctx, tt.args.conn) if (err != nil) != tt.wantErr { t.Errorf("ConnectVolume() error = %v, wantErr %v", err, tt.wantErr) @@ -174,7 +174,7 @@ func TestDisConnectVolume(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - nfs := &NFS{} + nfs := &Connector{} if err := nfs.DisConnectVolume(tt.args.ctx, tt.args.targetPath); (err != nil) != tt.wantErr { t.Errorf("DisConnectVolume() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/connector/nfsplus/nfs_plus.go b/connector/nfsplus/nfs_plus.go new file mode 100644 index 00000000..e92081da --- /dev/null +++ b/connector/nfsplus/nfs_plus.go @@ -0,0 +1,49 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package nfsplus to mount or unmount filesystem +package nfsplus + +import ( + "context" + + "huawei-csi-driver/connector" + "huawei-csi-driver/utils/log" +) + +// Connector to define a local lock when connect or disconnect, in order to preventing mounting and unmounting confusion +type Connector struct { +} + +func init() { + connector.RegisterConnector(connector.NFSPlusDriver, &Connector{}) +} + +// ConnectVolume to mount the source to target path, the source path can be block or nfs +// Example: +// +// mount /dev/sdb / +// mount / +func (nfsPlus *Connector) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { + log.AddContext(ctx).Infof("NFS+ Start to connect volume ==> connect info: %v", conn) + return tryConnectVolume(ctx, conn) +} + +// DisConnectVolume to unmount the target path +func (nfsPlus *Connector) DisConnectVolume(ctx context.Context, targetPath string) error { + log.AddContext(ctx).Infof("NFS+ Start to disconnect volume ==> target path is: %v", targetPath) + return tryDisConnectVolume(ctx, targetPath) +} diff --git a/connector/nfsplus/nfs_plus_helper.go b/connector/nfsplus/nfs_plus_helper.go new file mode 100644 index 00000000..dc9857ba --- /dev/null +++ b/connector/nfsplus/nfs_plus_helper.go @@ -0,0 +1,201 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package nfsplus to mount or unmount filesystem +package nfsplus + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "strings" + + "huawei-csi-driver/connector" + "huawei-csi-driver/connector/nfs" + "huawei-csi-driver/csi/backend/plugin" + pkgUtils "huawei-csi-driver/pkg/utils" + "huawei-csi-driver/utils" + "huawei-csi-driver/utils/log" +) + +const ( + nfsPlusMountCommand = "mount %s %s %s %s" + mountPathPermission = 0750 +) + +type connectorInfo struct { + sourcePath string + targetPath string + portals []string + localAdds string + remoteAdds string + mntFlags mountParam +} + +type mountParam struct { + dashT string + dashO string +} + +func tryConnectVolume(ctx context.Context, connMap map[string]interface{}) (string, error) { + connInfo, err := parseNFSPlusInfo(ctx, connMap) + if err != nil { + log.AddContext(ctx).Errorf("parse nfs plus info failed, connMap: %+v err: %v", connMap, err) + return "", err + } + + err = mountNFSPlus(ctx, connInfo) + if err != nil { + log.AddContext(ctx).Errorf("mount plus info failed, connMap: %+v err: %v", connInfo, err) + return "", err + } + + return "", nil +} + +func parseNFSPlusInfo(ctx context.Context, connectionProperties map[string]interface{}) (*connectorInfo, error) { + var con connectorInfo + sourcePath, srcPathExist := connectionProperties["sourcePath"].(string) + if !srcPathExist || sourcePath == "" { + return nil, pkgUtils.Errorln(ctx, "there are no source path in the connection info") + } + + targetPath, tgtPathExist := connectionProperties["targetPath"].(string) + if !tgtPathExist || targetPath == "" { + return nil, pkgUtils.Errorln(ctx, "there are no target path in the connection info") + } + + portals, portalsExist := connectionProperties["portals"].([]string) + if !portalsExist || len(portals) == 0 { + return nil, pkgUtils.Errorln(ctx, "there are no portals in the connection info") + } + + // format mount flags : remoteAdds + con.remoteAdds = strings.Join(portals, "~") + // format mount flags : mountFlag + mountFlags, _ := connectionProperties["mountFlags"].(string) + mountFlagsArr := make([]string, 0) + mountFlagsArr = append(mountFlagsArr, fmt.Sprintf("remoteaddrs=%s", con.remoteAdds)) + mountFlagsArr = append(mountFlagsArr, mountFlags) + + con.sourcePath = sourcePath + con.targetPath = targetPath + con.mntFlags = mountParam{dashO: strings.TrimSpace(strings.Join(mountFlagsArr, ",")), dashT: plugin.ProtocolNfs} + + log.AddContext(ctx).Infof("parseNFSPlusInfo success, data: %+v", con) + return &con, nil +} + +func checkMountPath(ctx context.Context, targetPath string) error { + if _, err := os.Stat(targetPath); err != nil && os.IsNotExist(err) { + if err := os.MkdirAll(targetPath, mountPathPermission); err != nil { + return pkgUtils.Errorln(ctx, "can not create a target path") + } + } + + return nil +} + +func mountNFSPlus(ctx context.Context, conn *connectorInfo) error { + var output string + var err error + err = checkMountPath(ctx, conn.targetPath) + if err != nil { + return pkgUtils.Errorf(ctx, "check mount path failed, err: %v", err) + } + + mountMap, err := connector.ReadMountPoints(ctx) + if err != nil { + return pkgUtils.Errorf(ctx, "get mount point map failed, err: %v", err) + } + + value, exist := mountMap[conn.targetPath] + if exist { + // check the filesystem by comparing the sourcePath and mountPath + if value == conn.sourcePath || path.Base(path.Dir(conn.targetPath)) == path.Base(path.Dir(conn.sourcePath)) || + nfs.ContainSourceDevice(ctx, conn.sourcePath, value) { + log.AddContext(ctx).Infof("Mount %s to %s is already exist", conn.sourcePath, conn.targetPath) + return nil + } + + return pkgUtils.Errorf(ctx, "The mount %s is already exist, source: %s realSource: %s", + conn.targetPath, conn.sourcePath, value) + } + + if conn.mntFlags.dashT != "" { + conn.mntFlags.dashT = fmt.Sprintf("-t %s", conn.mntFlags.dashT) + } + if conn.mntFlags.dashO != "" { + conn.mntFlags.dashO = fmt.Sprintf("-o %s", conn.mntFlags.dashO) + } + + output, err = utils.ExecShellCmd(ctx, fmt.Sprintf(nfsPlusMountCommand, conn.mntFlags.dashT, conn.mntFlags.dashO, + conn.sourcePath, conn.targetPath)) + if err != nil { + log.AddContext(ctx).Errorf("Mount %s to %s failed, error res: %s, error: %s", + conn.sourcePath, conn.targetPath, output, err) + return err + } + + return nil +} + +func tryDisConnectVolume(ctx context.Context, targetPath string) error { + err := unmountUnix(ctx, targetPath) + if err != nil { + return err + } + + return removeTargetPath(targetPath) +} + +func unmountUnix(ctx context.Context, targetPath string) error { + _, err := os.Stat(targetPath) + if err != nil && os.IsNotExist(err) { + return nil + } + + output, err := utils.ExecShellCmd(ctx, "umount %s", targetPath) + if err != nil && !(strings.Contains(output, "not mounted") || + strings.Contains(output, "not found")) { + log.AddContext(ctx).Errorf("Unmount %s error: %s", targetPath, output) + return err + } + + return nil +} + +func removeTargetPath(targetPath string) error { + _, err := os.Stat(targetPath) + if err != nil && os.IsNotExist(err) { + return nil + } + + if err != nil && !os.IsNotExist(err) { + msg := fmt.Sprintf("get target path %s state error %v", targetPath, err) + log.Errorln(msg) + return errors.New(msg) + } + + if err := os.RemoveAll(targetPath); err != nil { + msg := fmt.Sprintf("remove target path %s error %v", targetPath, err) + log.Errorln(msg) + return errors.New(msg) + } + return nil +} diff --git a/connector/nvme/nvme.go b/connector/nvme/nvme.go index 8ab9a891..53270309 100644 --- a/connector/nvme/nvme.go +++ b/connector/nvme/nvme.go @@ -25,7 +25,7 @@ import ( "huawei-csi-driver/utils/log" ) -// FCNVMe implements the Connector interface for FCNVMe protocol +// FCNVMe implements the connector.VolumeConnector for FCNVMe protocol type FCNVMe struct { mutex sync.Mutex } @@ -36,8 +36,9 @@ func init() { // ConnectVolume to mount the source to target path, the source path can be block or nfs // Example: -// mount /dev/sdb / -// mount / +// +// mount /dev/sdb / +// mount / func (fc *FCNVMe) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { log.AddContext(ctx).Infof("FC-NVMe Start to connect volume ==> connect info: %v", conn) tgtLunGuid, exist := conn["tgtLunGuid"].(string) diff --git a/connector/nvme/nvme_helper.go b/connector/nvme/nvme_helper.go index d5ffdfd2..d5bf8752 100644 --- a/connector/nvme/nvme_helper.go +++ b/connector/nvme/nvme_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import ( "huawei-csi-driver/utils/log" ) -var flushTimeInterval = 3 * time.Second +const flushTimeInterval = 3 * time.Second // PortWWNPair contains initiator wwn and target wwn type PortWWNPair struct { diff --git a/connector/nvme/nvme_test.go b/connector/nvme/nvme_test.go index bb698e81..19272a30 100644 --- a/connector/nvme/nvme_test.go +++ b/connector/nvme/nvme_test.go @@ -152,7 +152,8 @@ func TestDisConnectVolume(t *testing.T) { stubs.StubFunc(&connector.GetNVMePhysicalDevices, []string{}, nil) stubs.StubFunc(&connector.RemoveAllDevice, "test", nil) stubs.StubFunc(&connector.FlushDMDevice, nil) - stubs.Stub(&flushTimeInterval, time.Microsecond) + var interval = flushTimeInterval + stubs.Stub(&interval, time.Microsecond) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/connector/roce/roce.go b/connector/roce/roce.go index 12d07e2c..abd694f7 100644 --- a/connector/roce/roce.go +++ b/connector/roce/roce.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import ( "huawei-csi-driver/utils/log" ) -// RoCE implements the Connector interface for RoCE protocol -type RoCE struct { +// Connector implements the connector.VolumeConnector for Connector protocol +type Connector struct { } const ( @@ -34,14 +34,15 @@ const ( ) func init() { - connector.RegisterConnector(connector.RoCEDriver, &RoCE{}) + connector.RegisterConnector(connector.RoCEDriver, &Connector{}) } // ConnectVolume to mount the source to target path, the source path can be block or nfs // Example: -// mount /dev/sdb / -// mount / -func (roce *RoCE) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { +// +// mount /dev/sdb / +// mount / +func (roce *Connector) ConnectVolume(ctx context.Context, conn map[string]interface{}) (string, error) { log.AddContext(ctx).Infof("RoCE Start to connect volume ==> connect info: %v", conn) tgtLunGUID, exist := conn["tgtLunGuid"].(string) if !exist { @@ -51,7 +52,7 @@ func (roce *RoCE) ConnectVolume(ctx context.Context, conn map[string]interface{} } // DisConnectVolume to unmount the target path -func (roce *RoCE) DisConnectVolume(ctx context.Context, tgtLunGuid string) error { +func (roce *Connector) DisConnectVolume(ctx context.Context, tgtLunGuid string) error { log.AddContext(ctx).Infof("RoCE Start to disconnect volume ==> Volume Guid info: %v", tgtLunGuid) return connector.DisConnectVolumeCommon(ctx, tgtLunGuid, connector.RoCEDriver, tryDisConnectVolume) } diff --git a/connector/roce/roce_common.go b/connector/roce/roce_common.go index 4753ca63..40af1312 100644 --- a/connector/roce/roce_common.go +++ b/connector/roce/roce_common.go @@ -14,7 +14,7 @@ * limitations under the License. */ -// Package roce provide the way to connect/disconnect volume within NVMe over RoCE protocol +// Package roce provide the way to connect/disconnect volume within NVMe over Connector protocol package roce import ( @@ -69,7 +69,7 @@ func disconnectSessions(ctx context.Context, sessionPorts []string) error { "'{if($1>1) print 1; else print 0}'", nvmePort) output, err := utils.ExecShellCmd(ctx, cmd) if err != nil { - return utils.Errorf(ctx, "Disconnect RoCE target path %s failed, err: %v", nvmePort, err) + return utils.Errorf(ctx, "Disconnect Connector target path %s failed, err: %v", nvmePort, err) } outputSplit := strings.Split(output, "\n") diff --git a/connector/roce/roce_constants.go b/connector/roce/roce_constants.go index eeb80e0b..c488adc0 100644 --- a/connector/roce/roce_constants.go +++ b/connector/roce/roce_constants.go @@ -14,7 +14,7 @@ * limitations under the License. */ -// Package roce provide the way to connect/disconnect volume within NVMe over RoCE protocol +// Package roce provide the way to connect/disconnect volume within NVMe over Connector protocol package roce const sleepInternal = 2 diff --git a/connector/roce/roce_helper.go b/connector/roce/roce_helper.go index f7a5ee8c..bfbbea91 100644 --- a/connector/roce/roce_helper.go +++ b/connector/roce/roce_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,13 @@ import ( "runtime/debug" "strings" "sync" + "sync/atomic" "time" "huawei-csi-driver/connector" connutils "huawei-csi-driver/connector/utils" "huawei-csi-driver/utils" + "huawei-csi-driver/utils/concurrent" "huawei-csi-driver/utils/log" ) @@ -40,16 +42,17 @@ type connectorInfo struct { } type shareData struct { - stopConnecting bool - numLogin int64 - failedLogin int64 - stoppedThreads int64 - foundDevices []string - justAddedDevices []string + stopConnecting atomic.Bool + numLogin atomic.Int64 + failedLogin atomic.Int64 + stoppedThreads atomic.Int64 + foundDevices concurrent.Slice[string] + justAddedDevices concurrent.Slice[string] } const ( - connectTimeOut = 15 + connectTimeOut = 15 + subNqnSegmentCount = 2 ) func parseRoCEInfo(ctx context.Context, connectionProperties map[string]interface{}) (connectorInfo, error) { @@ -97,8 +100,8 @@ func getTargetNQN(ctx context.Context, tgtPortal string) (string, error) { lines := strings.Split(output, "\n") for _, line := range lines { if strings.Contains(line, "subnqn") { - splits := strings.SplitN(line, ":", 2) - if len(splits) == 2 && splits[0] == "subnqn" { + splits := strings.SplitN(line, ":", subNqnSegmentCount) + if len(splits) == subNqnSegmentCount && splits[0] == "subnqn" { tgtNqn = strings.Trim(splits[1], " ") break } @@ -150,20 +153,20 @@ func connectVol(ctx context.Context, targetNQN, err := getTargetNQN(ctx, tgtPortal) if err != nil { log.AddContext(ctx).Errorf("Cannot discover nvme target %s, reason: %v", tgtPortal, err) - nvmeShareData.failedLogin += 1 - nvmeShareData.stoppedThreads += 1 + nvmeShareData.failedLogin.Add(1) + nvmeShareData.stoppedThreads.Add(1) return } err = connectRoCEPortal(ctx, existSessions, tgtPortal, targetNQN) if err != nil { log.AddContext(ctx).Errorf("connect roce portal %s error, reason: %v", tgtPortal, err) - nvmeShareData.failedLogin += 1 - nvmeShareData.stoppedThreads += 1 + nvmeShareData.failedLogin.Add(1) + nvmeShareData.stoppedThreads.Add(1) return } - nvmeShareData.numLogin += 1 + nvmeShareData.numLogin.Add(1) var device string for i := 1; i < 4; i++ { nvmeConnectInfo, err := connector.GetSubSysInfo(ctx) @@ -177,7 +180,7 @@ func connectVol(ctx context.Context, log.AddContext(ctx).Errorf("Get device of guid %s error: %v", tgtLunGUID, err) break } - if device != "" || nvmeShareData.stopConnecting { + if device != "" || nvmeShareData.stopConnecting.Load() { break } @@ -190,17 +193,18 @@ func connectVol(ctx context.Context, } if device != "" { - nvmeShareData.foundDevices = append(nvmeShareData.foundDevices, device) - nvmeShareData.justAddedDevices = append(nvmeShareData.justAddedDevices, device) + nvmeShareData.foundDevices.Append(device) + nvmeShareData.justAddedDevices.Append(device) } - nvmeShareData.stoppedThreads += 1 + + nvmeShareData.stoppedThreads.Add(1) return } func scanSingle(ctx context.Context, nvmeShareData *shareData) { log.AddContext(ctx).Infoln("Enter function:scanSingle") for i := 0; i < 15; i++ { - if len(nvmeShareData.foundDevices) != 0 { + if nvmeShareData.foundDevices.Len() != 0 { break } time.Sleep(time.Second * intNumTwo) @@ -285,7 +289,7 @@ func tryConnectVolume(ctx context.Context, connMap map[string]interface{}) (stri mPath = scanDevice(ctx, conn, nvmeShareData) - nvmeShareData.stopConnecting = true + nvmeShareData.stopConnecting.Store(true) wait.Wait() return verifyDevice(ctx, conn, nvmeShareData, mPath) @@ -310,7 +314,7 @@ func scanUpNVMeMultiPath(ctx context.Context, conn connectorInfo, nvmeShareData allThread := int64(len(conn.tgtPortals)) for isThreadNotStoppedOrFoundDevices(allThread, nvmeShareData) && isThreadNotFinishedOrDeviceNotObtained(device, allThread, nvmeShareData) { - if timeout == 0 && len(nvmeShareData.foundDevices) != 0 && nvmeShareData.stoppedThreads == allThread { + if timeout == 0 && nvmeShareData.foundDevices.Len() != 0 && nvmeShareData.stoppedThreads.Load() == allThread { log.AddContext(ctx).Infof("All connection threads finished, "+ "giving %d seconds for ultrapath* to appear.", connectTimeOut) timeout = time.Now().Unix() + connectTimeOut @@ -330,11 +334,11 @@ func scanUpNVMeMultiPath(ctx context.Context, conn connectorInfo, nvmeShareData } func isThreadNotStoppedOrFoundDevices(allThread int64, nvmeShareData *shareData) bool { - return nvmeShareData.stoppedThreads != allThread || len(nvmeShareData.foundDevices) != 0 + return nvmeShareData.stoppedThreads.Load() != allThread || nvmeShareData.foundDevices.Len() != 0 } func isThreadNotFinishedOrDeviceNotObtained(device string, allThread int64, nvmeShareData *shareData) bool { - return nvmeShareData.numLogin+nvmeShareData.failedLogin != allThread || device == "" + return nvmeShareData.numLogin.Load()+nvmeShareData.failedLogin.Load() != allThread || device == "" } func verifyDevice(ctx context.Context, @@ -342,12 +346,12 @@ func verifyDevice(ctx context.Context, nvmeShareData *shareData, mPath string) (string, error) { log.AddContext(ctx).Infof("Enter function:verifyDevice, mPath:%s", mPath) - if nvmeShareData.foundDevices == nil { + if nvmeShareData.foundDevices.Values() == nil { return "", utils.Errorf(ctx, connector.VolumeDeviceNotFound) } if !conn.volumeUseMultiPath { - device := fmt.Sprintf("/dev/%s", nvmeShareData.foundDevices[0]) + device := fmt.Sprintf("/dev/%s", nvmeShareData.foundDevices.Get(0)) err := connector.VerifySingleDevice(ctx, device, conn.tgtLunGUID, connector.VolumeDeviceNotFound, tryDisConnectVolume) if err != nil { diff --git a/connector/roce/roce_models.go b/connector/roce/roce_models.go index 801416a1..61cab8f8 100644 --- a/connector/roce/roce_models.go +++ b/connector/roce/roce_models.go @@ -14,5 +14,5 @@ * limitations under the License. */ -// Package roce provide the way to connect/disconnect volume within NVMe over RoCE protocol +// Package roce provide the way to connect/disconnect volume within NVMe over Connector protocol package roce diff --git a/connector/roce/roce_ultrapath_nvme_helper.go b/connector/roce/roce_ultrapath_nvme_helper.go index 801416a1..61cab8f8 100644 --- a/connector/roce/roce_ultrapath_nvme_helper.go +++ b/connector/roce/roce_ultrapath_nvme_helper.go @@ -14,5 +14,5 @@ * limitations under the License. */ -// Package roce provide the way to connect/disconnect volume within NVMe over RoCE protocol +// Package roce provide the way to connect/disconnect volume within NVMe over Connector protocol package roce diff --git a/connector/ultrapath.go b/connector/ultrapath.go index cb998ed6..aec73a0f 100644 --- a/connector/ultrapath.go +++ b/connector/ultrapath.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,15 @@ const ( nxupLunMapFile = "/proc/nxup_lun_map_a" nxupLunMapFileWithUpgrade = "/proc/nxup_lun_map_b" deviceDelete = "deleted" + waitingDeletePeriod = 5 * time.Second + + splitSegment = 5 + hctlIndex = 3 + hctlLength = 4 + lunIdKeyIndex = 2 + lunIdIndex = 3 + devTupleKeyIndex = 2 + devTupleNameIndex = 4 ) type ultrapathDeviceInfo struct { @@ -101,7 +110,7 @@ func CleanDeviceByLunId(ctx context.Context, lunId string, targets []string) err if needCleanDevice.deviceName == deviceDelete { log.AddContext(ctx).Infoln("device name deleted, waiting 5s") - time.Sleep(5 * time.Second) + time.Sleep(waitingDeletePeriod) } return nil @@ -178,12 +187,14 @@ func parseDevice(line string, lunId string, deviceHctlMap map[string][]string, if deviceTupleMap == nil { return } + splitValue := strings.Split(line, "=") - if len(splitValue) > 5 { - hctl := strings.Split(splitValue[3], ":") - if len(hctl) == 4 && hctl[3] == lunId { - addToMap(deviceHctlMap, splitValue[2], splitValue[3]) - deviceTupleMap[splitValue[2]] = ultrapathDeviceTuple{name: splitValue[4], id: splitValue[1]} + if len(splitValue) > splitSegment { + hctl := strings.Split(splitValue[hctlIndex], ":") + if len(hctl) == hctlLength && hctl[lunIdIndex] == lunId { + addToMap(deviceHctlMap, splitValue[lunIdKeyIndex], splitValue[lunIdIndex]) + deviceTupleMap[splitValue[devTupleKeyIndex]] = ultrapathDeviceTuple{ + name: splitValue[devTupleNameIndex], id: splitValue[1]} } } } diff --git a/connector/ultrapath_vlun.go b/connector/ultrapath_vlun.go new file mode 100644 index 00000000..497ac1bb --- /dev/null +++ b/connector/ultrapath_vlun.go @@ -0,0 +1,156 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package connector + +import ( + "context" + "fmt" + "regexp" + "strings" + "time" + + "huawei-csi-driver/utils/log" +) + +const ( + fieldsLengthToGetVLun = 5 + diskNameDeleted = "deleted" +) + +const ( + idIndex = iota + diskIndex + nameIndex + wwnIndex + statusIndex +) + +// reHctl is the regex to find the physical device, for example "6:0:0:2". +var reHctl = regexp.MustCompile("\\d:\\d:\\d:\\d") + +// UltrapathVLun is the vLun of ultrapath. +type UltrapathVLun struct { + ID string + Disk string + Name string + WWN string + Status string + upType string +} + +// GetUltrapathVLunByWWN gets the ultraPath vLun by the WWN of device. +func GetUltrapathVLunByWWN(ctx context.Context, upType, wwn string) (*UltrapathVLun, error) { + res, err := GetUltraPathInfoByDevName(ctx, upType, wwn) + if err != nil { + if err.Error() == exitStatus1 && res == "" { + return nil, nil + } + + return nil, err + } + + fields := strings.Fields(res) + if len(fields) < fieldsLengthToGetVLun { + return nil, fmt.Errorf("want [%d] fields to get vLun, but got [%d]", fieldsLengthToGetVLun, len(fields)) + } + + return &UltrapathVLun{ + ID: fields[idIndex], + Disk: fields[diskIndex], + Name: fields[nameIndex], + WWN: fields[wwnIndex], + Status: fields[statusIndex], + upType: upType, + }, nil +} + +// CleanResidualPath clean residual path of vLun. +func (vLun *UltrapathVLun) CleanResidualPath(ctx context.Context) error { + if vLun.Disk != diskNameDeleted { + return nil + } + + log.AddContext(ctx).Infof("the disk [%s] status is [%s], need to clean", vLun.Disk, vLun.Status) + if err := vLun.cleanPhysicalPaths(ctx); err != nil { + return err + } + + log.AddContext(ctx).Infoln("device deleted, waiting 5s") + time.Sleep(waitingDeletePeriod) + + return nil +} + +func (vLun *UltrapathVLun) cleanPhysicalPaths(ctx context.Context) error { + paths, err := vLun.getPhysicalPaths(ctx) + if err != nil { + log.AddContext(ctx).Warningf("get physical paths failed, error: %v", err) + return err + } + + for _, p := range paths { + if err := p.clean(ctx); err != nil { + log.AddContext(ctx).Warningf("clean path [%s] failed, error: %v", p.hctl, err) + } + } + + return nil +} + +func (vLun *UltrapathVLun) getPhysicalPaths(ctx context.Context) ([]physicalPath, error) { + output, err := runUpCommand(ctx, vLun.upType, "show vlun id=%s | grep -w Path", vLun.ID) + if err != nil { + return nil, err + } + + var physicalPaths []physicalPath + for _, subPath := range strings.Split(output, "\n") { + if strings.TrimSpace(subPath) == "" { + continue + } + + physicalPaths = append(physicalPaths, physicalPath{ + hctl: reHctl.FindString(subPath), + status: getStatusFromPathResult(subPath), + }) + } + + return physicalPaths, nil +} + +type physicalPath struct { + hctl string + status string +} + +func (p physicalPath) clean(ctx context.Context) error { + log.AddContext(ctx).Infof("start to delete physical device [%s], status is [%s]", p.hctl, p.status) + if err := deletePhysicalDevice(ctx, p.hctl); err != nil { + log.AddContext(ctx).Warningf("delete physical device [%s] failed, error is %v", p.hctl, err) + } + + return nil +} + +func getStatusFromPathResult(result string) string { + segments := strings.Split(result, ":") + if len(segments) > 0 { + return strings.TrimSpace(segments[len(segments)-1]) + } + + return "" +} diff --git a/connector/ultrapath_vlun_test.go b/connector/ultrapath_vlun_test.go new file mode 100644 index 00000000..74272f96 --- /dev/null +++ b/connector/ultrapath_vlun_test.go @@ -0,0 +1,148 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package connector + +import ( + "context" + "errors" + "testing" + + "github.com/agiledragon/gomonkey/v2" + "github.com/stretchr/testify/require" +) + +func TestGetUltraPathVLunByWWN(t *testing.T) { + ctx := context.Background() + wwn := "testWwn" + + t.Run("success", func(t *testing.T) { + // arrange + commandResult := " 1 deleted testDisk testWwn Fault 1.00GB " + + " 0B/0B Huawei.Storage 61 0/2" + expectedVLun := &UltrapathVLun{ + ID: "1", + Disk: "deleted", + Name: "testDisk", + WWN: "testWwn", + Status: "Fault", + upType: UltraPathCommand, + } + + // mock + //runUpCommand() + gomonkey.ApplyFuncReturn(runUpCommand, commandResult, nil) + + // action + vLun, err := GetUltrapathVLunByWWN(ctx, UltraPathCommand, wwn) + + // assert + require.NoError(t, err) + require.Equal(t, expectedVLun, vLun) + }) + + t.Run("no residual path", func(t *testing.T) { + // arrange + + // mock + gomonkey.ApplyFuncReturn(runUpCommand, nil, errors.New(exitStatus1)) + + // action + vLun, err := GetUltrapathVLunByWWN(ctx, UltraPathCommand, "testWwn") + + // assert + require.NoError(t, err) + require.Nil(t, vLun) + }) +} + +func TestUltraPathVLun_CleanResidualPath(t *testing.T) { + t.Run("doesn't need to clean", func(t *testing.T) { + // arrange + vLun := &UltrapathVLun{ + ID: "1", + Disk: "sda", + Name: "testDisk", + WWN: "testWwn", + Status: diskStatusNormal, + upType: UltraPathCommand, + } + + // action + err := vLun.CleanResidualPath(context.Background()) + + // assert + require.NoError(t, err) + }) + + t.Run("clean successfully", func(t *testing.T) { + // arrange + vLun := &UltrapathVLun{ + ID: "1", + Disk: "deleted", + Name: "testDisk", + WWN: "testWwn", + Status: "Fault", + upType: UltraPathCommand, + } + vLunDetails := "Path 62 [7:0:0:2] (up-270) : Fault\nPath 63 [6:0:0:2] (up-269) : Fault" + expectedPhyDevices := []string{"7:0:0:2", "6:0:0:2"} + var actualPhyDevices []string + + // mock + gomonkey.ApplyFuncReturn(runUpCommand, vLunDetails, nil) + gomonkey.ApplyFunc(deletePhysicalDevice, func(ctx context.Context, phyDevice string) error { + actualPhyDevices = append(actualPhyDevices, phyDevice) + return nil + }) + + // action + err := vLun.CleanResidualPath(context.Background()) + + // assert + require.NoError(t, err) + require.Equal(t, expectedPhyDevices, actualPhyDevices) + }) + + t.Run("clean physical paths", func(t *testing.T) { + // arrange + vLun := &UltrapathVLun{ + ID: "1", + Disk: "deleted", + Name: "testDisk", + WWN: "testWwn", + Status: "Fault", + upType: UltraPathCommand, + } + vLunDetails := "Path 62 [7:0:0:2] (up-270) : Fault\nPath 63 [6:0:0:2] (up-269) : Normal\n " + expectedPhyDevices := []string{"7:0:0:2", "6:0:0:2"} + var actualPhyDevices []string + + // mock + gomonkey.ApplyFuncReturn(runUpCommand, vLunDetails, nil) + gomonkey.ApplyFunc(deletePhysicalDevice, func(ctx context.Context, phyDevice string) error { + actualPhyDevices = append(actualPhyDevices, phyDevice) + return nil + }) + + // action + err := vLun.CleanResidualPath(context.Background()) + + // assert + require.NoError(t, err) + require.Equal(t, expectedPhyDevices, actualPhyDevices) + }) +} diff --git a/connector/utils/lock/lock.go b/connector/utils/lock/lock.go index 8b63b1c9..7b645b20 100644 --- a/connector/utils/lock/lock.go +++ b/connector/utils/lock/lock.go @@ -21,7 +21,6 @@ import ( "context" "fmt" "path/filepath" - "sync" "time" "huawei-csi-driver/csi/app" @@ -29,18 +28,9 @@ import ( "huawei-csi-driver/utils/log" ) -// Lock is a special lock interface provided for disk management -type Lock interface { - SyncLock(lockName string) - - SyncUnlock(lockName string) -} - var ( - curDriverName string - lockMutex sync.Mutex - semaphoreMap map[string]*utils.Semaphore - lockDir = fmt.Sprintf("%s/lock/", lockDirPrefix) + semaphoreMap map[string]*utils.Semaphore + dir = fmt.Sprintf("%s/lock/", lockDirPrefix) ) const ( @@ -64,7 +54,7 @@ const ( // InitLock provide three semaphores for device connect, disconnect and expand func InitLock(driverName string) error { - err := checkLockPath(lockDir) + err := checkLockPath(dir) if err != nil { return err } @@ -74,7 +64,6 @@ func InitLock(driverName string) error { disConnectVolume: utils.NewSemaphore(app.GetGlobalConfig().ConnectorThreads), extendVolume: utils.NewSemaphore(app.GetGlobalConfig().ConnectorThreads), } - curDriverName = driverName log.Infoln("Init lock success.") return nil } @@ -83,19 +72,19 @@ func InitLock(driverName string) error { func SyncLock(ctx context.Context, lockName, operationType string) error { startTime := time.Now() - err := createLockDir(filepath.Dir(lockDir)) + err := createLockDir(filepath.Dir(dir)) if err != nil { return fmt.Errorf("create dir failed, reason: %s", err) } - err = waitGetLock(ctx, lockDir, lockName) + err = waitGetLock(ctx, dir, lockName) if err != nil { return err } err = waitGetSemaphore(ctx, operationType) if err != nil { - newErr := deleteLockFile(ctx, lockDir, lockName) + newErr := deleteLockFile(ctx, dir, lockName) if newErr != nil { log.AddContext(ctx).Errorln("new error occurred when delete lock file") return newErr @@ -112,7 +101,7 @@ func SyncUnlock(ctx context.Context, lockName, operationType string) error { startTime := time.Now() releaseSemaphore(ctx, operationType) - err := deleteLockFile(ctx, lockDir, lockName) + err := deleteLockFile(ctx, dir, lockName) if err != nil { return err } diff --git a/connector/utils/lock/lock_utils.go b/connector/utils/lock/lock_utils.go index 51d94d81..bb97f72e 100644 --- a/connector/utils/lock/lock_utils.go +++ b/connector/utils/lock/lock_utils.go @@ -24,6 +24,7 @@ import ( "os" "path/filepath" "strings" + "sync" "time" pkgUtils "huawei-csi-driver/pkg/utils" @@ -31,6 +32,8 @@ import ( "huawei-csi-driver/utils/log" ) +var mutex sync.Mutex + func clearLockFile(fileDir string) error { files, err := ioutil.ReadDir(fileDir) if err != nil { @@ -106,8 +109,8 @@ func createLockFile(ctx context.Context, filePath, lockName string) error { func deleteLockFile(ctx context.Context, lockDir, lockName string) error { log.AddContext(ctx).Infoln("DeleteLockFile start to get lock") - lockMutex.Lock() - defer lockMutex.Unlock() + mutex.Lock() + defer mutex.Unlock() log.AddContext(ctx).Infoln("DeleteLockFile finish to get lock") filePath := fmt.Sprintf("%s%s%s", lockDir, lockNamePrefix, lockName) exist := isFileExist(filePath) @@ -122,8 +125,8 @@ func waitGetLock(ctx context.Context, lockDir, lockName string) error { filePath := fmt.Sprintf("%s%s%s", lockDir, lockNamePrefix, lockName) log.AddContext(ctx).Infoln("WaitGetLock start to get lock") err := utils.WaitUntil(func() (bool, error) { - lockMutex.Lock() - defer lockMutex.Unlock() + mutex.Lock() + defer mutex.Unlock() exist := isFileExist(filePath) if !exist { diff --git a/csi/app/config/config.go b/csi/app/config/config.go index 275af9f2..2c167eeb 100644 --- a/csi/app/config/config.go +++ b/csi/app/config/config.go @@ -37,7 +37,6 @@ type loggingConfig struct { type serviceConfig struct { Controller bool EnableLeaderElection bool - EnableLabel bool Endpoint string DrEndpoint string @@ -93,8 +92,8 @@ type extenderConfig struct { VolumeModifyReconcileDelay time.Duration } -// Config contains the configurations from env -type Config struct { +// AppConfig contains the configurations from env +type AppConfig struct { loggingConfig serviceConfig connectorConfig @@ -104,13 +103,13 @@ type Config struct { // CompletedConfig contains the env and config type CompletedConfig struct { - *Config + *AppConfig K8sUtils k8sutils.Interface BackendUtils clientSet.Interface } -// Complete the Config and return the CompletedConfig -func (cfg *Config) Complete() (*CompletedConfig, error) { +// Complete the AppConfig and return the CompletedConfig +func (cfg *AppConfig) Complete() (*CompletedConfig, error) { k8sUtils, err := k8sutils.NewK8SUtils(cfg.KubeConfig, cfg.VolumeNamePrefix, map[string]string{"provisioner": cfg.DriverName}) if err != nil { @@ -125,7 +124,7 @@ func (cfg *Config) Complete() (*CompletedConfig, error) { } return &CompletedConfig{ - Config: cfg, + AppConfig: cfg, K8sUtils: k8sUtils, BackendUtils: backendUtils, }, nil @@ -133,5 +132,5 @@ func (cfg *Config) Complete() (*CompletedConfig, error) { // Print the configuration when before the service func (cfg *CompletedConfig) Print() { - logrus.Infof("Controller manager config %+v", cfg.Config) + logrus.Infof("Controller manager config %+v", cfg.AppConfig) } diff --git a/csi/app/config/config_mock.go b/csi/app/config/config_mock.go index 93a065f9..d21a8ced 100644 --- a/csi/app/config/config_mock.go +++ b/csi/app/config/config_mock.go @@ -34,7 +34,7 @@ const ( // MockCompletedConfig for unit test func MockCompletedConfig() *CompletedConfig { return &CompletedConfig{ - Config: &Config{ + AppConfig: &AppConfig{ mockLoggingConfig(), mockServiceConfig(), mockConnectorConfig(), @@ -60,7 +60,6 @@ func mockServiceConfig() serviceConfig { return serviceConfig{ Controller: false, EnableLeaderElection: false, - EnableLabel: false, Endpoint: "", DrEndpoint: "", diff --git a/csi/app/options/connector.go b/csi/app/options/connector.go index df376722..d38ac079 100644 --- a/csi/app/options/connector.go +++ b/csi/app/options/connector.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,12 +29,19 @@ const ( hwUltraPath = "HW-UltraPath" hwUltraPathNVMe = "HW-UltraPath-NVMe" - defaultCleanupTimeout = 240 - defaultScanVolumeTimeout = 3 - defaultConnectorThreads = 4 + defaultCleanupTimeout = 240 + defaultScanVolumeTimeout = 3 + defaultConnectorThreads = 4 + defaultExecCommandTimeout = 30 minThreads = 1 maxThreads = 10 + + minScanVolumeTimeout = 1 + maxScanVolumeTimeout = 600 + + minExecCommandTimeout = 1 + maxExecCommandTimeout = 600 ) type connectorOptions struct { @@ -73,24 +80,24 @@ func (opt *connectorOptions) AddFlags(ff *flag.FlagSet) { hwUltraPathNVMe, "Multipath software for roce/fc-nvme block volumes") ff.IntVar(&opt.deviceCleanupTimeout, "deviceCleanupTimeout", - 240, + defaultCleanupTimeout, "Timeout interval in seconds for stale device cleanup") ff.IntVar(&opt.scanVolumeTimeout, "scan-volume-timeout", - 3, + defaultScanVolumeTimeout, "The timeout for waiting for multipath aggregation when DM-multipath is used on the host") ff.IntVar(&opt.connectorThreads, "connector-threads", - 4, + defaultConnectorThreads, "The concurrency supported during disk operations.") ff.BoolVar(&opt.allPathOnline, "all-path-online", false, "Whether to check the number of online paths for DM-multipath aggregation, default false") ff.IntVar(&opt.execCommandTimeout, "exec-command-timeout", - 30, + defaultExecCommandTimeout, "The timeout for running command on host") } // ApplyFlags assign the connector flags -func (opt *connectorOptions) ApplyFlags(cfg *config.Config) { +func (opt *connectorOptions) ApplyFlags(cfg *config.AppConfig) { cfg.VolumeUseMultiPath = opt.volumeUseMultiPath cfg.ScsiMultiPathType = opt.scsiMultiPathType cfg.NvmeMultiPathType = opt.nvmeMultiPathType @@ -141,7 +148,7 @@ func (opt *connectorOptions) validateNvmeMultiPathType() error { } func (opt *connectorOptions) validateScanVolumeTimeout() error { - if opt.scanVolumeTimeout < 1 || opt.scanVolumeTimeout > 600 { + if opt.scanVolumeTimeout < minScanVolumeTimeout || opt.scanVolumeTimeout > maxScanVolumeTimeout { return fmt.Errorf("the value of scanVolumeTimeout ranges from 1 to 600, current is: %d", opt.scanVolumeTimeout) } @@ -156,7 +163,7 @@ func (opt *connectorOptions) validateConnectorThreads() error { return nil } func (opt *connectorOptions) validateExecCommandTimeout() error { - if opt.execCommandTimeout < 1 || opt.execCommandTimeout > 600 { + if opt.execCommandTimeout < minExecCommandTimeout || opt.execCommandTimeout > maxExecCommandTimeout { return fmt.Errorf("the value of execCommandTimeout ranges from 1 to 600, current is: %d", opt.scanVolumeTimeout) } diff --git a/csi/app/options/extender.go b/csi/app/options/extender.go index 92d7aa65..a8b06f9e 100644 --- a/csi/app/options/extender.go +++ b/csi/app/options/extender.go @@ -65,7 +65,7 @@ func (opt *extenderOptions) AddFlags(ff *flag.FlagSet) { } // ApplyFlags assign the extender flags -func (opt *extenderOptions) ApplyFlags(cfg *config.Config) { +func (opt *extenderOptions) ApplyFlags(cfg *config.AppConfig) { cfg.VolumeModifyRetryBaseDelay = opt.volumeModifyRetryBaseDelay cfg.VolumeModifyRetryMaxDelay = opt.volumeModifyRetryMaxDelay cfg.VolumeModifyReconcileDelay = opt.volumeModifyReconcileDelay diff --git a/csi/app/options/k8s.go b/csi/app/options/k8s.go index fd61cbd3..24ef5994 100644 --- a/csi/app/options/k8s.go +++ b/csi/app/options/k8s.go @@ -44,7 +44,7 @@ func (opt *k8sOptions) AddFlags(ff *flag.FlagSet) { } // ApplyFlags assign the connector flags -func (opt *k8sOptions) ApplyFlags(cfg *config.Config) { +func (opt *k8sOptions) ApplyFlags(cfg *config.AppConfig) { cfg.Namespace = opt.namespace } diff --git a/csi/app/options/logging.go b/csi/app/options/logging.go index 72da4955..5ddbfad1 100644 --- a/csi/app/options/logging.go +++ b/csi/app/options/logging.go @@ -73,7 +73,7 @@ func (opt *loggingOptions) AddFlags(ff *flag.FlagSet) { } // ApplyFlags assign the log flags -func (opt *loggingOptions) ApplyFlags(cfg *config.Config) { +func (opt *loggingOptions) ApplyFlags(cfg *config.AppConfig) { cfg.MaxBackups = opt.maxBackups cfg.LoggingModule = opt.loggingModule cfg.LogFileDir = opt.logFileDir diff --git a/csi/app/options/options.go b/csi/app/options/options.go index a3c6a35d..9991c51c 100644 --- a/csi/app/options/options.go +++ b/csi/app/options/options.go @@ -54,7 +54,7 @@ func (opt *optionsManager) AddFlags(ff *flag.FlagSet) { } // ApplyFlags assign the flags -func (opt *optionsManager) ApplyFlags(cfg *config.Config) { +func (opt *optionsManager) ApplyFlags(cfg *config.AppConfig) { opt.logOption.ApplyFlags(cfg) opt.connectorOption.ApplyFlags(cfg) opt.serviceOption.ApplyFlags(cfg) @@ -81,12 +81,12 @@ func (opt *optionsManager) ValidateFlags() error { } // Config set all configuration -func (opt *optionsManager) Config() (*config.Config, error) { +func (opt *optionsManager) Config() (*config.AppConfig, error) { if err := opt.ValidateFlags(); err != nil { return nil, err } - cfg := config.Config{} + cfg := config.AppConfig{} opt.ApplyFlags(&cfg) return &cfg, nil } diff --git a/csi/app/options/options_test.go b/csi/app/options/options_test.go index 0a3d6bc4..1fc2ce86 100644 --- a/csi/app/options/options_test.go +++ b/csi/app/options/options_test.go @@ -63,7 +63,7 @@ func TestConfig(t *testing.T) { } } -func compareLogOptions(envCfg *config.Config) error { +func compareLogOptions(envCfg *config.AppConfig) error { expectLogOptions := NewLoggingOptions() actuallyLogOptions := &loggingOptions{ logFileSize: envCfg.LogFileSize, @@ -80,7 +80,7 @@ func compareLogOptions(envCfg *config.Config) error { return nil } -func compareConnectorOptions(envCfg *config.Config) error { +func compareConnectorOptions(envCfg *config.AppConfig) error { expectConnectorOptions := NewConnectorOptions() actuallyConnectorOptions := &connectorOptions{ volumeUseMultiPath: true, diff --git a/csi/app/options/service.go b/csi/app/options/service.go index 5c6c125a..4cede849 100644 --- a/csi/app/options/service.go +++ b/csi/app/options/service.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,11 +26,20 @@ import ( "huawei-csi-driver/pkg/constants" ) +const ( + defaultRpcTimeout = 1 * time.Minute + defaultWorkerThreads = 10 + defaultReSyncPeriods = 2 * time.Minute + defaultLeaderRetryPeriod = 2 * time.Second + defaultLeaderRenewDeadline = 6 * time.Second + defaultLeaderLeaseDuration = 8 * time.Second + defaultBackendUpdateIntervalSeconds = 60 +) + // serviceOptions include service's configuration type serviceOptions struct { controller bool enableLeaderElection bool - enableLabel bool driverName string endpoint string @@ -72,8 +81,8 @@ func (opt *serviceOptions) AddFlags(ff *flag.FlagSet) { ff.StringVar(&opt.driverName, "driver-name", constants.DefaultDriverName, "CSI driver name") - ff.IntVar(&opt.backendUpdateInterval, "backend-update-interval", - 60, "The interval seconds to update backends status. Default is 60 seconds") + ff.IntVar(&opt.backendUpdateInterval, "backend-update-interval", defaultBackendUpdateIntervalSeconds, + "The interval seconds to update backends status. Default is 60 seconds") ff.StringVar(&opt.kubeConfig, "kubeconfig", "", "absolute path to the kubeconfig file") ff.StringVar(&opt.nodeName, "nodename", @@ -89,28 +98,25 @@ func (opt *serviceOptions) AddFlags(ff *flag.FlagSet) { "The port of webhook server") ff.StringVar(&opt.webHookAddress, "web-hook-address", "", "The Address of webhook server") - ff.BoolVar(&opt.enableLabel, "enable-label", false, - "csi enable label") ff.BoolVar(&opt.enableLeaderElection, "enable-leader-election", false, "backend enable leader election") - ff.DurationVar(&opt.leaderLeaseDuration, "leader-lease-duration", 8*time.Second, + ff.DurationVar(&opt.leaderLeaseDuration, "leader-lease-duration", defaultLeaderLeaseDuration, "backend leader lease duration") - ff.DurationVar(&opt.leaderRenewDeadline, "leader-renew-deadline", 6*time.Second, + ff.DurationVar(&opt.leaderRenewDeadline, "leader-renew-deadline", defaultLeaderRenewDeadline, "backend leader renew deadline") - ff.DurationVar(&opt.leaderRetryPeriod, "leader-retry-period", 2*time.Second, + ff.DurationVar(&opt.leaderRetryPeriod, "leader-retry-period", defaultLeaderRetryPeriod, "backend leader retry period") - ff.DurationVar(&opt.reSyncPeriod, "re-sync-period", 2*time.Minute, "reSync interval of the controller") - ff.IntVar(&opt.workerThreads, "worker-threads", 10, "number of worker threads.") - ff.DurationVar(&opt.timeout, "timeout", 1*time.Minute, "timeout for any RPCs") + ff.DurationVar(&opt.reSyncPeriod, "re-sync-period", defaultReSyncPeriods, "reSync interval of the controller") + ff.IntVar(&opt.workerThreads, "worker-threads", defaultWorkerThreads, "number of worker threads.") + ff.DurationVar(&opt.timeout, "timeout", defaultRpcTimeout, "timeout for any RPCs") ff.StringVar(&opt.kubeletVolumeDevicesDirName, "kubelet-volume-devices-dir-name", constants.DefaultKubeletVolumeDevicesDirName, "The dir name of volume devices") } // ApplyFlags assign the service flags -func (opt *serviceOptions) ApplyFlags(cfg *config.Config) { +func (opt *serviceOptions) ApplyFlags(cfg *config.AppConfig) { cfg.Endpoint = opt.endpoint cfg.DrEndpoint = opt.drEndpoint - cfg.EnableLabel = opt.enableLabel cfg.Controller = opt.controller cfg.DriverName = opt.driverName cfg.BackendUpdateInterval = opt.backendUpdateInterval diff --git a/csi/backend/backend.go b/csi/backend/backend.go index cca7211c..412aba2a 100644 --- a/csi/backend/backend.go +++ b/csi/backend/backend.go @@ -146,8 +146,7 @@ func BuildBackend(ctx context.Context, content v1.StorageBackendContent) (*model config, err := GetStorageBackendInfo(ctx, pkgUtils.MakeMetaWithNamespace(ns, name), - content.Spec.ConfigmapMeta, content.Spec.SecretMeta, - content.Spec.CertSecret, content.Spec.UseCert) + NewGetBackendInfoArgsFromContent(&content)) if err != nil { return nil, err } @@ -208,6 +207,7 @@ func NewBackend(backendName string, config map[string]interface{}) (*model.Backe replicaBackend, _ := config["replicaBackend"].(string) metroBackend, _ := config["metroBackend"].(string) accountName, _ := config["accountName"].(string) + contentName, _ := config["contentName"].(string) // while config hyperMetro, the metroBackend must config, hyperMetroDomain or metrovStorePairID should be config if ((metroDomain != "" || metrovStorePairID != "") && metroBackend == "") || @@ -217,6 +217,7 @@ func NewBackend(backendName string, config map[string]interface{}) (*model.Backe return &model.Backend{ Name: backendName, + ContentName: contentName, Storage: storage, Available: false, SupportedTopologies: supportedTopologies, @@ -449,7 +450,7 @@ func updateSelectPool(requestSize int64, parameters map[string]interface{}, sele // when the allocType is thin, do not change the FreeCapacity. if allocType == "thick" { freeCapacity := utils.ParseIntWithDefault(selectPool.Capacities["FreeCapacity"], 10, 64, 0) - selectPool.Capacities["FreeCapacity"] = strconv.FormatInt(int64(rune(freeCapacity-requestSize)), 10) + selectPool.Capacities["FreeCapacity"] = strconv.FormatInt(freeCapacity-requestSize, 10) } } diff --git a/csi/backend/backend_get.go b/csi/backend/backend_get.go index 3235045c..628c1ce2 100644 --- a/csi/backend/backend_get.go +++ b/csi/backend/backend_get.go @@ -23,10 +23,12 @@ import ( "errors" "fmt" + coreV1 "k8s.io/api/core/v1" + + xuanwuV1 "huawei-csi-driver/client/apis/xuanwu/v1" "huawei-csi-driver/csi/app" pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/utils/log" - coreV1 "k8s.io/api/core/v1" ) // GetBackendConfigmap used to get Configmap @@ -85,34 +87,64 @@ func addSecretInfo(secret *coreV1.Secret, storageConfig map[string]interface{}) return nil } +// GetBackendInfoArgs is the arguments to get backend info. +type GetBackendInfoArgs struct { + contentName string + configmapMeta string + secretMeta string + certSecret string + useCert bool +} + +// NewGetBackendInfoArgsFromClaim used to new get backend info arguments from StorageBackendClaim +func NewGetBackendInfoArgsFromClaim(claim *xuanwuV1.StorageBackendClaim) GetBackendInfoArgs { + return GetBackendInfoArgs{ + configmapMeta: claim.Spec.ConfigMapMeta, + secretMeta: claim.Spec.SecretMeta, + certSecret: claim.Spec.CertSecret, + useCert: claim.Spec.UseCert, + } +} + +// NewGetBackendInfoArgsFromContent used to new get backend info arguments from StorageBackendContent +func NewGetBackendInfoArgsFromContent(content *xuanwuV1.StorageBackendContent) GetBackendInfoArgs { + return GetBackendInfoArgs{ + contentName: content.Name, + configmapMeta: content.Spec.ConfigmapMeta, + secretMeta: content.Spec.SecretMeta, + certSecret: content.Spec.CertSecret, + useCert: content.Spec.UseCert, + } +} + // GetStorageBackendInfo used to get storage config info -func GetStorageBackendInfo(ctx context.Context, backendID, configmapMeta, secretMeta, certSecret string, - useCert bool) (map[string]interface{}, error) { +func GetStorageBackendInfo(ctx context.Context, backendID string, args GetBackendInfoArgs) (map[string]any, error) { log.AddContext(ctx).Infof("start GetStorageBackendInfo: %s.", backendID) - backendMapData, err := GetBackendConfigmapMap(ctx, configmapMeta) + backendMapData, err := GetBackendConfigmapMap(ctx, args.configmapMeta) if err != nil { - msg := fmt.Sprintf("get backend %s failed, error %v", configmapMeta, err) + msg := fmt.Sprintf("get backend %s failed, error %v", args.configmapMeta, err) log.AddContext(ctx).Errorln(msg) return nil, errors.New(msg) } - secret, err := pkgUtils.GetBackendSecret(ctx, secretMeta) + secret, err := pkgUtils.GetBackendSecret(ctx, args.secretMeta) if err != nil { - msg := fmt.Sprintf("GetBackendSecret for secret %s failed, error %v", secretMeta, err) + msg := fmt.Sprintf("GetBackendSecret for secret %s failed, error %v", args.secretMeta, err) log.AddContext(ctx).Errorln(msg) return nil, errors.New(msg) } err = addSecretInfo(secret, backendMapData) if err != nil { - msg := fmt.Sprintf("addSecretInfo for secret %s failed, error %v", secretMeta, err) + msg := fmt.Sprintf("addSecretInfo for secret %s failed, error %v", args.secretMeta, err) log.AddContext(ctx).Errorln(msg) return nil, errors.New(msg) } backendMapData["backendID"] = backendID - backendMapData["useCert"] = useCert - backendMapData["certSecret"] = certSecret + backendMapData["useCert"] = args.useCert + backendMapData["certSecret"] = args.certSecret + backendMapData["contentName"] = args.contentName return backendMapData, nil } diff --git a/csi/backend/backend_test.go b/csi/backend/backend_test.go index a78d2c26..7c4dd5d9 100644 --- a/csi/backend/backend_test.go +++ b/csi/backend/backend_test.go @@ -24,6 +24,7 @@ import ( "github.com/agiledragon/gomonkey/v2" "github.com/prashantv/gostub" + "github.com/stretchr/testify/require" "huawei-csi-driver/csi/app" cfg "huawei-csi-driver/csi/app/config" @@ -869,3 +870,20 @@ func TestValidateBackend(t *testing.T) { t.Errorf("test validateBackend error %v", err) } } + +func TestUpdateSelectPool(t *testing.T) { + // arrange + var ( + requestSize = int64(16106127360) + parameters = map[string]any{"allocType": "thick"} + selectPool = &model.StoragePool{Capacities: map[string]string{"FreeCapacity": "1138971639808"}} + + expectedCapacity = "1122865512448" // 1138971639808 - 16106127360 + ) + + // act + updateSelectPool(requestSize, parameters, selectPool) + + // assert + require.Equal(t, expectedCapacity, selectPool.Capacities["FreeCapacity"]) +} diff --git a/csi/backend/handler/backend_cache_wrapper.go b/csi/backend/handler/backend_cache_wrapper.go index 6db0dccc..ae49d42a 100644 --- a/csi/backend/handler/backend_cache_wrapper.go +++ b/csi/backend/handler/backend_cache_wrapper.go @@ -46,7 +46,7 @@ type CacheWrapper struct { // NewCacheWrapper init instance of CacheWrapper func NewCacheWrapper() *CacheWrapper { - return &CacheWrapper{cache.BackendCacheProvider} + return &CacheWrapper{BackendCacheInterface: cache.BackendCacheProvider} } // AddBackendToCache init a backend and add to cache diff --git a/csi/backend/handler/backend_register.go b/csi/backend/handler/backend_register.go index 79d87537..8776fc73 100644 --- a/csi/backend/handler/backend_register.go +++ b/csi/backend/handler/backend_register.go @@ -31,6 +31,7 @@ type BackendRegisterInterface interface { FetchAndRegisterAllBackend(ctx context.Context) FetchAndRegisterOneBackend(ctx context.Context, name string, checkOnline bool) (*model.Backend, error) LoadOrRegisterOneBackend(ctx context.Context, name string) (*model.Backend, error) + LoadOrRebuildOneBackend(ctx context.Context, name, contentName string) (*model.Backend, error) RemoveRegisteredOneBackend(ctx context.Context, name string) UpdateOrRegisterOneBackend(ctx context.Context, sbct *v1.StorageBackendContent) error } @@ -65,6 +66,24 @@ func (b *BackendRegister) LoadOrRegisterOneBackend(ctx context.Context, name str return b.FetchAndRegisterOneBackend(ctx, name, true) } +// LoadOrRebuildOneBackend if the backend has been already in cache, and doesn't need to rebuild, return it directly. +// Otherwise, fetch and register the backend again. +func (b *BackendRegister) LoadOrRebuildOneBackend(ctx context.Context, + name, contentName string) (*model.Backend, error) { + bk, exists := b.cacheHandler.Load(name) + if exists && !bk.NeedRebuild(contentName) { + return &bk, nil + } + + if bk.NeedRebuild(contentName) { + log.AddContext(ctx).Infof("The content name of backend [%s] has changed from [%s] to [%s], need rebuild", + bk.Name, bk.ContentName, contentName) + b.cacheHandler.Delete(ctx, name) + } + + return b.FetchAndRegisterOneBackend(ctx, name, true) +} + // FetchAndRegisterAllBackend fetch all backends in the kubernetes and register them to cache. func (b *BackendRegister) FetchAndRegisterAllBackend(ctx context.Context) { contents, err := b.fetchHandler.FetchAllBackends(ctx) diff --git a/csi/backend/handler/backend_service.go b/csi/backend/handler/backend_service.go index 0173fe14..a8a70d1e 100644 --- a/csi/backend/handler/backend_service.go +++ b/csi/backend/handler/backend_service.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import ( "strconv" "huawei-csi-driver/lib/drcsi" + "huawei-csi-driver/pkg/constants" pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/utils/log" ) @@ -34,7 +35,7 @@ type StorageBackendDetails struct { // StorageServiceInterface query backend operation set type StorageServiceInterface interface { - GetBackendDetails(ctx context.Context, name string) (StorageBackendDetails, error) + GetBackendDetails(ctx context.Context, name, contentName string) (StorageBackendDetails, error) } // StorageHandler backend query handler @@ -54,8 +55,9 @@ func NewStorageHandler() *StorageHandler { } // GetBackendDetails query backend details -func (s *StorageHandler) GetBackendDetails(ctx context.Context, name string) (StorageBackendDetails, error) { - bk, err := s.register.LoadOrRegisterOneBackend(ctx, name) +func (s *StorageHandler) GetBackendDetails(ctx context.Context, + name, contentName string) (StorageBackendDetails, error) { + bk, err := s.register.LoadOrRebuildOneBackend(ctx, name, contentName) if err != nil { log.AddContext(ctx).Warningf("load cache backend %s failed, error: %v", name, err) return StorageBackendDetails{}, err @@ -84,7 +86,7 @@ func (s *StorageHandler) GetBackendDetails(ctx context.Context, name string) (St capacities := make(map[string]string) poolCapabilityInt64Map := pkgUtils.ConvertToMapValueX[int64](ctx, poolCapabilityMap[pool.GetName()]) for k, v := range poolCapabilityInt64Map { - capacities[k] = strconv.FormatInt(v, 10) + capacities[k] = strconv.FormatInt(v, constants.DefaultIntBase) } poolCapacities = append(poolCapacities, &drcsi.Pool{ Name: pool.Name, diff --git a/csi/backend/model/model.go b/csi/backend/model/model.go index bb7eeda6..f94e1a46 100644 --- a/csi/backend/model/model.go +++ b/csi/backend/model/model.go @@ -34,9 +34,10 @@ type StorageBackendTuple struct { // Backend for storage type Backend struct { Name string + ContentName string Storage string Available bool - Plugin plugin.Plugin + Plugin plugin.StoragePlugin Pools []*StoragePool Parameters map[string]interface{} SupportedTopologies []map[string]string @@ -67,6 +68,11 @@ func (b *Backend) UpdatePools(ctx context.Context, sbct *xuanwuV1.StorageBackend } } +// NeedRebuild checks whether the backend needs rebuild +func (b *Backend) NeedRebuild(contentName string) bool { + return b.ContentName != contentName +} + // SelectPoolPair for pool pair type SelectPoolPair struct { Local *StoragePool diff --git a/csi/backend/model/storage_pool.go b/csi/backend/model/storage_pool.go index f04a79c5..4bd89142 100644 --- a/csi/backend/model/storage_pool.go +++ b/csi/backend/model/storage_pool.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ type StoragePool struct { Parent string Capabilities map[string]bool Capacities map[string]string - Plugin plugin.Plugin + Plugin plugin.StoragePlugin } func (p *StoragePool) setCapacity(k string, v string) { diff --git a/csi/backend/model/storage_pool_test.go b/csi/backend/model/storage_pool_test.go index be452db2..d7a1c37a 100644 --- a/csi/backend/model/storage_pool_test.go +++ b/csi/backend/model/storage_pool_test.go @@ -147,7 +147,6 @@ func TestStoragePool_UpdatePoolCapabilities(t *testing.T) { "SupportApplicationType": true, "SupportClone": true, "SupportMetroNAS": false, - "SupportLabel": true, } // act diff --git a/csi/backend/plugin/fusionstorage-nas.go b/csi/backend/plugin/fusionstorage-nas.go index 09c80501..891cdede 100644 --- a/csi/backend/plugin/fusionstorage-nas.go +++ b/csi/backend/plugin/fusionstorage-nas.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import ( "huawei-csi-driver/utils/log" ) -// FusionStorageNasPlugin implements storage Plugin interface +// FusionStorageNasPlugin implements storage StoragePlugin interface type FusionStorageNasPlugin struct { FusionStoragePlugin portal string @@ -39,7 +39,7 @@ func init() { } // NewPlugin used to create new plugin -func (p *FusionStorageNasPlugin) NewPlugin() Plugin { +func (p *FusionStorageNasPlugin) NewPlugin() StoragePlugin { return &FusionStorageNasPlugin{} } @@ -70,7 +70,8 @@ func (p *FusionStorageNasPlugin) Init(ctx context.Context, config map[string]int return nil } -func (p *FusionStorageNasPlugin) updateNasCapacity(ctx context.Context, params, parameters map[string]interface{}) error { +func (p *FusionStorageNasPlugin) updateNasCapacity(ctx context.Context, + params, parameters map[string]interface{}) error { size, exist := parameters["size"].(int64) if !exist { return utils.Errorf(ctx, "the size does not exist in parameters %v", parameters) @@ -134,7 +135,6 @@ func (p *FusionStorageNasPlugin) UpdateBackendCapabilities(ctx context.Context) "SupportQoS": true, "SupportQuota": true, "SupportClone": false, - "SupportLabel": false, } err := p.updateNFS4Capability(ctx, capabilities) diff --git a/csi/backend/plugin/fusionstorage-san.go b/csi/backend/plugin/fusionstorage-san.go index d94c0d84..bddc5e3b 100644 --- a/csi/backend/plugin/fusionstorage-san.go +++ b/csi/backend/plugin/fusionstorage-san.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ import ( "huawei-csi-driver/utils/log" ) -// FusionStorageSanPlugin implements storage Plugin interface +// FusionStorageSanPlugin implements storage StoragePlugin interface type FusionStorageSanPlugin struct { FusionStoragePlugin hosts map[string]string @@ -51,7 +51,7 @@ func init() { } // NewPlugin used to create new plugin -func (p *FusionStorageSanPlugin) NewPlugin() Plugin { +func (p *FusionStorageSanPlugin) NewPlugin() StoragePlugin { return &FusionStorageSanPlugin{ hosts: make(map[string]string), } @@ -119,7 +119,7 @@ func (p *FusionStorageSanPlugin) getParams(name string, params := map[string]interface{}{ "name": name, "description": parameters["description"].(string), - "capacity": utils.RoundUpSize(parameters["size"].(int64), CAPACITY_UNIT), + "capacity": utils.RoundUpSize(parameters["size"].(int64), CapacityUnit), } paramKeys := []string{ @@ -146,9 +146,9 @@ func (p *FusionStorageSanPlugin) CreateVolume(ctx context.Context, name string, size, ok := parameters["size"].(int64) // for fusionStorage block, the unit is MiB - if !ok || !utils.IsCapacityAvailable(size, CAPACITY_UNIT) { + if !ok || !utils.IsCapacityAvailable(size, CapacityUnit) { msg := fmt.Sprintf("Create Volume: the capacity %d is not an integer or not multiple of %d.", - size, CAPACITY_UNIT) + size, CapacityUnit) log.AddContext(ctx).Errorln(msg) return nil, errors.New(msg) } @@ -183,12 +183,12 @@ func (p *FusionStorageSanPlugin) DeleteVolume(ctx context.Context, name string) // ExpandVolume used to expand volume func (p *FusionStorageSanPlugin) ExpandVolume(ctx context.Context, name string, size int64) (bool, error) { // for fusionStorage block, the unit is MiB - if !utils.IsCapacityAvailable(size, CAPACITY_UNIT) { + if !utils.IsCapacityAvailable(size, CapacityUnit) { return false, utils.Errorf(ctx, "Expand Volume: the capacity %d is not an integer multiple of %d.", - size, CAPACITY_UNIT) + size, CapacityUnit) } san := volume.NewSAN(p.cli) - newSize := utils.TransVolumeCapacity(size, CAPACITY_UNIT) + newSize := utils.TransVolumeCapacity(size, CapacityUnit) isAttach, err := san.Expand(ctx, name, newSize) return isAttach, err } @@ -196,7 +196,14 @@ func (p *FusionStorageSanPlugin) ExpandVolume(ctx context.Context, name string, // AttachVolume attach volume to node and return storage mapping info. func (p *FusionStorageSanPlugin) AttachVolume(ctx context.Context, name string, parameters map[string]interface{}) (map[string]interface{}, error) { - localAttacher := attacher.NewAttacher(p.cli, p.protocol, "csi", p.portals, p.hosts, p.alua) + localAttacher := attacher.NewAttacher(attacher.VolumeAttacherConfig{ + Cli: p.cli, + Protocol: p.protocol, + Invoker: "csi", + Portals: p.portals, + Hosts: p.hosts, + Alua: p.alua, + }) mappingInfo, err := localAttacher.ControllerAttach(ctx, name, parameters) if err != nil { log.AddContext(ctx).Errorf("attach volume %s error: %v", name, err) @@ -210,7 +217,14 @@ func (p *FusionStorageSanPlugin) AttachVolume(ctx context.Context, name string, func (p *FusionStorageSanPlugin) DetachVolume(ctx context.Context, name string, parameters map[string]interface{}) error { - localAttacher := attacher.NewAttacher(p.cli, p.protocol, "csi", p.portals, p.hosts, p.alua) + localAttacher := attacher.NewAttacher(attacher.VolumeAttacherConfig{ + Cli: p.cli, + Protocol: p.protocol, + Invoker: "csi", + Portals: p.portals, + Hosts: p.hosts, + Alua: p.alua, + }) _, err := localAttacher.ControllerDetach(ctx, name, parameters) if err != nil { log.AddContext(ctx).Errorf("Detach volume %s error: %v", name, err) @@ -222,7 +236,7 @@ func (p *FusionStorageSanPlugin) DetachVolume(ctx context.Context, func (p *FusionStorageSanPlugin) mutexReleaseClient(ctx context.Context, plugin *FusionStorageSanPlugin, - cli *client.Client) { + cli *client.RestClient) { plugin.clientMutex.Lock() defer plugin.clientMutex.Unlock() plugin.clientCount-- @@ -232,7 +246,7 @@ func (p *FusionStorageSanPlugin) mutexReleaseClient(ctx context.Context, } } -func (p *FusionStorageSanPlugin) releaseClient(ctx context.Context, cli *client.Client) { +func (p *FusionStorageSanPlugin) releaseClient(ctx context.Context, cli *client.RestClient) { if p.storageOnline { p.mutexReleaseClient(ctx, p, cli) } @@ -246,7 +260,6 @@ func (p *FusionStorageSanPlugin) UpdateBackendCapabilities(ctx context.Context) "SupportThick": false, "SupportQoS": true, "SupportClone": true, - "SupportLabel": false, } return capabilities, nil, nil } diff --git a/csi/backend/plugin/fusionstorage.go b/csi/backend/plugin/fusionstorage.go index 8b86d428..93f5cc20 100644 --- a/csi/backend/plugin/fusionstorage.go +++ b/csi/backend/plugin/fusionstorage.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,12 +31,12 @@ import ( ) const ( - // CAPACITY_UNIT unit of capacity - CAPACITY_UNIT int64 = 1024 * 1024 + // CapacityUnit unit of capacity + CapacityUnit int64 = 1024 * 1024 fileCapacityUnit int64 = 1024 - // PROTOCOL_DPC protocol DPC string - PROTOCOL_DPC = "dpc" + // ProtocolDpc protocol DPC string + ProtocolDpc = "dpc" ) const ( @@ -49,7 +49,7 @@ const ( // FusionStoragePlugin defines the plugin for Fusion storage type FusionStoragePlugin struct { basePlugin - cli *client.Client + cli *client.RestClient } func (p *FusionStoragePlugin) init(ctx context.Context, config map[string]interface{}, keepLogin bool) error { @@ -88,7 +88,7 @@ func (p *FusionStoragePlugin) getParams(name string, params := map[string]interface{}{ "name": name, "description": parameters["description"].(string), - "capacity": utils.RoundUpSize(parameters["size"].(int64), CAPACITY_UNIT), + "capacity": utils.RoundUpSize(parameters["size"].(int64), CapacityUnit), } paramKeys := []string{ @@ -147,9 +147,9 @@ func (p *FusionStoragePlugin) updatePoolCapabilities(ctx context.Context, poolNa totalCapacity := int64(pool["totalCapacity"].(float64)) usedCapacity := int64(pool["usedCapacity"].(float64)) capability := map[string]interface{}{ - string(xuanwuv1.FreeCapacity): (totalCapacity - usedCapacity) * CAPACITY_UNIT, - string(xuanwuv1.TotalCapacity): totalCapacity * CAPACITY_UNIT, - string(xuanwuv1.UsedCapacity): usedCapacity * CAPACITY_UNIT, + string(xuanwuv1.FreeCapacity): (totalCapacity - usedCapacity) * CapacityUnit, + string(xuanwuv1.TotalCapacity): totalCapacity * CapacityUnit, + string(xuanwuv1.UsedCapacity): usedCapacity * CapacityUnit, } capabilities[name] = capability } @@ -171,7 +171,8 @@ func (p *FusionStoragePlugin) Logout(ctx context.Context) { } } -func (p *FusionStoragePlugin) getNewClientConfig(ctx context.Context, config map[string]interface{}) (*client.NewClientConfig, error) { +func (p *FusionStoragePlugin) getNewClientConfig(ctx context.Context, + config map[string]interface{}) (*client.NewClientConfig, error) { newClientConfig := &client.NewClientConfig{} configUrls, exist := config["urls"].([]interface{}) if !exist || len(configUrls) <= 0 { diff --git a/csi/backend/plugin/oceanstor-dtree.go b/csi/backend/plugin/oceanstor-dtree.go index f7208ee8..77c8e5f1 100644 --- a/csi/backend/plugin/oceanstor-dtree.go +++ b/csi/backend/plugin/oceanstor-dtree.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ const ( DTreeStorage = "oceanstor-dtree" ) -// OceanstorDTreePlugin implements storage Plugin interface +// OceanstorDTreePlugin implements storage StoragePlugin interface type OceanstorDTreePlugin struct { OceanstorPlugin @@ -49,7 +49,7 @@ func init() { } // NewPlugin used to create new plugin -func (p *OceanstorDTreePlugin) NewPlugin() Plugin { +func (p *OceanstorDTreePlugin) NewPlugin() StoragePlugin { return &OceanstorDTreePlugin{} } @@ -153,7 +153,7 @@ func (p *OceanstorDTreePlugin) ExpandDTreeVolume(ctx context.Context, params map } parentName, _ := utils.ToStringWithFlag(params["parentname"]) - err := dTree.Expand(ctx, parentName, dTreeName, p.vStoreId, 0, spaceHardQuota) + err := dTree.Expand(ctx, parentName, dTreeName, p.vStoreId, spaceHardQuota) if err != nil { log.AddContext(ctx).Errorf("expand dTree volume failed, ") return false, err @@ -258,7 +258,6 @@ func (p *OceanstorDTreePlugin) UpdateBackendCapabilities(ctx context.Context) (m } // close dTree pvc label switch - capabilities[string(constants.SupportLabel)] = false capabilities[string(constants.SupportMetro)] = false capabilities[string(constants.SupportMetroNAS)] = false capabilities[string(constants.SupportReplication)] = false diff --git a/csi/backend/plugin/oceanstor-nas.go b/csi/backend/plugin/oceanstor-nas.go index deac0865..3708c03c 100644 --- a/csi/backend/plugin/oceanstor-nas.go +++ b/csi/backend/plugin/oceanstor-nas.go @@ -50,9 +50,9 @@ const ( ConsistentSnapshotsSpecification = "128" ) -var supportConsistentSnapshotsVersions = []string{"6.1.6"} +var supportConsistentSnapshotsMinVersion = "6.1.6" -// OceanstorNasPlugin implements storage Plugin interface +// OceanstorNasPlugin implements storage StoragePlugin interface type OceanstorNasPlugin struct { OceanstorPlugin portals []string @@ -69,7 +69,7 @@ func init() { } // NewPlugin used to create new plugin -func (p *OceanstorNasPlugin) NewPlugin() Plugin { +func (p *OceanstorNasPlugin) NewPlugin() StoragePlugin { return &OceanstorNasPlugin{} } @@ -97,10 +97,10 @@ func (p *OceanstorNasPlugin) Init(ctx context.Context, config map[string]interfa return err } - if protocol == ProtocolNfsPlus && p.cli.GetStorageVersion() < constants.MinVersionSupportLabel { + if protocol == ProtocolNfsPlus && p.cli.GetStorageVersion() < constants.MinVersionSupportNfsPlus { p.Logout(ctx) - return errors.New("only oceanstor nas version gte 6.1.7 support nfs_plus") + return errors.New("only oceanstor nas version gte 6.1.7 support nfs plus") } return nil @@ -236,10 +236,10 @@ func (p *OceanstorNasPlugin) getVstoreCapacity(ctx context.Context) (map[string] var nasCapacityQuota, nasFreeCapacityQuota int64 if totalStr, ok := vStore["nasCapacityQuota"].(string); ok { - nasCapacityQuota, err = strconv.ParseInt(totalStr, 10, 64) + nasCapacityQuota, err = strconv.ParseInt(totalStr, constants.DefaultIntBase, constants.DefaultIntBitSize) } if freeStr, ok := vStore["nasFreeCapacityQuota"].(string); ok { - nasFreeCapacityQuota, err = strconv.ParseInt(freeStr, 10, 64) + nasFreeCapacityQuota, err = strconv.ParseInt(freeStr, constants.DefaultIntBase, constants.DefaultIntBitSize) } if err != nil { log.AddContext(ctx).Warningf("parse vstore quota failed, error: %v", err) @@ -254,14 +254,14 @@ func (p *OceanstorNasPlugin) getVstoreCapacity(ctx context.Context) (map[string] } return map[string]interface{}{ - string(xuanwuV1.FreeCapacity): nasFreeCapacityQuota * 512, - string(xuanwuV1.TotalCapacity): nasCapacityQuota * 512, - string(xuanwuV1.UsedCapacity): (nasCapacityQuota - nasFreeCapacityQuota) * 512, + string(xuanwuV1.FreeCapacity): nasFreeCapacityQuota * constants.AllocationUnitBytes, + string(xuanwuV1.TotalCapacity): nasCapacityQuota * constants.AllocationUnitBytes, + string(xuanwuV1.UsedCapacity): (nasCapacityQuota - nasFreeCapacityQuota) * constants.AllocationUnitBytes, }, nil } // UpdateMetroRemotePlugin used to convert metroRemotePlugin to OceanstorSanPlugin -func (p *OceanstorNasPlugin) UpdateMetroRemotePlugin(ctx context.Context, remote Plugin) { +func (p *OceanstorNasPlugin) UpdateMetroRemotePlugin(ctx context.Context, remote StoragePlugin) { var ok bool p.metroRemotePlugin, ok = remote.(*OceanstorNasPlugin) if !ok { @@ -401,14 +401,14 @@ func (p *OceanstorNasPlugin) updateHyperMetroCapability(ctx context.Context, return nil } -func (p *OceanstorNasPlugin) updateConsistentSnapshotCapability( - capabilities, specifications map[string]interface{}) error { - var supportConsistentSnapshot bool - if utils.StringContain(p.cli.GetStorageVersion(), supportConsistentSnapshotsVersions) { - supportConsistentSnapshot = true - specifications["ConsistentSnapshotLimits"] = ConsistentSnapshotsSpecification +func (p *OceanstorNasPlugin) updateConsistentSnapshotCapability(capabilities, specifications map[string]any) error { + if p.cli.GetStorageVersion() < supportConsistentSnapshotsMinVersion { + capabilities["SupportConsistentSnapshot"] = false + return nil } - capabilities["SupportConsistentSnapshot"] = supportConsistentSnapshot + + capabilities["SupportConsistentSnapshot"] = true + specifications["ConsistentSnapshotLimits"] = ConsistentSnapshotsSpecification return nil } diff --git a/csi/backend/plugin/oceanstor-nas_test.go b/csi/backend/plugin/oceanstor-nas_test.go index 60e57b63..431eb6f2 100644 --- a/csi/backend/plugin/oceanstor-nas_test.go +++ b/csi/backend/plugin/oceanstor-nas_test.go @@ -22,9 +22,7 @@ import ( "reflect" "testing" - "bou.ke/monkey" "github.com/agiledragon/gomonkey/v2" - "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/require" "huawei-csi-driver/storage/oceanstor/client" @@ -62,17 +60,17 @@ func TestInit(t *testing.T) { } var cli *client.BaseClient - monkey.PatchInstanceMethod(reflect.TypeOf(cli), "Logout", - func(*client.BaseClient, context.Context) {}) - monkey.PatchInstanceMethod(reflect.TypeOf(cli), "Login", - func(*client.BaseClient, context.Context) error { - return nil - }) - monkey.PatchInstanceMethod(reflect.TypeOf(cli), "SetSystemInfo", - func(*client.BaseClient, context.Context) error { - return nil - }) - defer monkey.UnpatchAll() + p := gomonkey.ApplyMethod(reflect.TypeOf(cli), "Logout", + func(*client.BaseClient, context.Context) {}). + ApplyMethod(reflect.TypeOf(cli), "Login", + func(*client.BaseClient, context.Context) error { + return nil + }). + ApplyMethod(reflect.TypeOf(cli), "SetSystemInfo", + func(*client.BaseClient, context.Context) error { + return nil + }) + defer p.Reset() for _, tt := range tests { var p = &OceanstorNasPlugin{} @@ -85,12 +83,12 @@ func TestInit(t *testing.T) { } func TestValidate(t *testing.T) { - convey.Convey("Empty", t, func() { + t.Run("Empty", func(t *testing.T) { err := mockOceanstorNasPlugin.Validate(ctx, map[string]interface{}{}) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) - convey.Convey("Normal", t, func() { + t.Run("Normal", func(t *testing.T) { portals := []interface{}{"127.0.0.1"} parameters := map[string]interface{}{ "protocol": "nfs", @@ -113,7 +111,7 @@ func TestValidate(t *testing.T) { defer m.Reset() err := mockOceanstorNasPlugin.Validate(ctx, config) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) } @@ -273,3 +271,35 @@ func TestGetLocal2HyperMetroParameters_EmptyParam(t *testing.T) { // assert require.Equal(t, nil, err) } + +func TestOceanstorNasPluginUpdateConsistentSnapshotCapability(t *testing.T) { + // arrange + cases := []struct { + version string + supported bool + }{ + {version: "6.0.1", supported: false}, + {version: "6.1.0", supported: false}, + {version: "6.1.2", supported: false}, + {version: "6.1.3", supported: false}, + {version: "6.1.5", supported: false}, + {version: "6.1.6", supported: true}, + {version: "6.1.7", supported: true}, + {version: "6.1.8", supported: true}, + } + var genNas = func(version string) *OceanstorNasPlugin { + return &OceanstorNasPlugin{OceanstorPlugin: OceanstorPlugin{cli: &client.BaseClient{StorageVersion: version}}} + } + + for _, c := range cases { + capabilities := map[string]any{} + specifications := map[string]any{} + + // act + err := genNas(c.version).updateConsistentSnapshotCapability(capabilities, specifications) + + // assert + require.NoError(t, err) + require.Equal(t, c.supported, capabilities["SupportConsistentSnapshot"]) + } +} diff --git a/csi/backend/plugin/oceanstor-san.go b/csi/backend/plugin/oceanstor-san.go index 75175ab3..490f208a 100644 --- a/csi/backend/plugin/oceanstor-san.go +++ b/csi/backend/plugin/oceanstor-san.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ const ( reflectResultLength = 2 ) -// OceanstorSanPlugin implements storage Plugin interface +// OceanstorSanPlugin implements storage StoragePlugin interface type OceanstorSanPlugin struct { OceanstorPlugin protocol string @@ -69,7 +69,7 @@ func init() { } // NewPlugin used to create new plugin -func (p *OceanstorSanPlugin) NewPlugin() Plugin { +func (p *OceanstorSanPlugin) NewPlugin() StoragePlugin { return &OceanstorSanPlugin{} } @@ -173,7 +173,7 @@ func (p *OceanstorSanPlugin) ExpandVolume(ctx context.Context, name string, size return false, errors.New(msg) } san := p.getSanObj() - newSize := utils.TransVolumeCapacity(size, 512) + newSize := utils.TransVolumeCapacity(size, constants.AllocationUnitBytes) isAttach, err := san.Expand(ctx, name, newSize) return isAttach, err } @@ -222,9 +222,22 @@ func (p *OceanstorSanPlugin) metroHandler(ctx context.Context, req handlerReques } } - localAttacher := attacher.NewAttacher(p.product, req.localCli, p.protocol, "csi", p.portals, p.alua) - remoteAttacher := attacher.NewAttacher(p.metroRemotePlugin.product, req.metroCli, p.metroRemotePlugin.protocol, - "csi", p.metroRemotePlugin.portals, p.metroRemotePlugin.alua) + localAttacher := attacher.NewAttacher(attacher.VolumeAttacherConfig{ + Product: p.product, + Cli: req.localCli, + Protocol: p.protocol, + Invoker: "csi", + Portals: p.portals, + Alua: p.alua, + }) + remoteAttacher := attacher.NewAttacher(attacher.VolumeAttacherConfig{ + Product: p.metroRemotePlugin.product, + Cli: req.metroCli, + Protocol: p.metroRemotePlugin.protocol, + Invoker: "csi", + Portals: p.metroRemotePlugin.portals, + Alua: p.metroRemotePlugin.alua, + }) metroAttacher := attacher.NewMetroAttacher(localAttacher, remoteAttacher, p.protocol) lunName, ok := req.lun["NAME"].(string) @@ -237,10 +250,15 @@ func (p *OceanstorSanPlugin) metroHandler(ctx context.Context, req handlerReques } func (p *OceanstorSanPlugin) commonHandler(ctx context.Context, - plugin *OceanstorSanPlugin, lun, parameters map[string]interface{}, - method string) ([]reflect.Value, error) { - commonAttacher := attacher.NewAttacher(plugin.product, plugin.cli, plugin.protocol, "csi", - plugin.portals, plugin.alua) + plugin *OceanstorSanPlugin, lun, parameters map[string]any, method string) ([]reflect.Value, error) { + commonAttacher := attacher.NewAttacher(attacher.VolumeAttacherConfig{ + Product: plugin.product, + Cli: plugin.cli, + Protocol: plugin.protocol, + Invoker: "csi", + Portals: plugin.portals, + Alua: plugin.alua, + }) lunName, ok := lun["NAME"].(string) if !ok { @@ -408,10 +426,10 @@ func (p *OceanstorSanPlugin) getVstoreCapacity(ctx context.Context) (map[string] var sanCapacityQuota, sanFreeCapacityQuota int64 if totalStr, ok := vStore["sanCapacityQuota"].(string); ok { - sanCapacityQuota, err = strconv.ParseInt(totalStr, 10, 64) + sanCapacityQuota, err = strconv.ParseInt(totalStr, constants.DefaultIntBase, constants.DefaultIntBitSize) } if freeStr, ok := vStore["sanFreeCapacityQuota"].(string); ok { - sanFreeCapacityQuota, err = strconv.ParseInt(freeStr, 10, 64) + sanFreeCapacityQuota, err = strconv.ParseInt(freeStr, constants.DefaultIntBase, constants.DefaultIntBitSize) } if err != nil { log.AddContext(ctx).Warningf("parse vstore quota failed, error: %v", err) @@ -424,14 +442,14 @@ func (p *OceanstorSanPlugin) getVstoreCapacity(ctx context.Context) (map[string] } return map[string]interface{}{ - string(xuanwuV1.FreeCapacity): sanFreeCapacityQuota * 512, - string(xuanwuV1.TotalCapacity): sanCapacityQuota * 512, - string(xuanwuV1.UsedCapacity): (sanCapacityQuota - sanFreeCapacityQuota) * 512, + string(xuanwuV1.FreeCapacity): sanFreeCapacityQuota * constants.AllocationUnitBytes, + string(xuanwuV1.TotalCapacity): sanCapacityQuota * constants.AllocationUnitBytes, + string(xuanwuV1.UsedCapacity): (sanCapacityQuota - sanFreeCapacityQuota) * constants.AllocationUnitBytes, }, nil } // UpdateMetroRemotePlugin used to convert metroRemotePlugin to OceanstorSanPlugin -func (p *OceanstorSanPlugin) UpdateMetroRemotePlugin(ctx context.Context, remote Plugin) { +func (p *OceanstorSanPlugin) UpdateMetroRemotePlugin(ctx context.Context, remote StoragePlugin) { var ok bool p.metroRemotePlugin, ok = remote.(*OceanstorSanPlugin) if !ok { @@ -484,7 +502,8 @@ func (p *OceanstorSanPlugin) mutexGetClient(ctx context.Context) (client.BaseCli return p.cli, err } -func (p *OceanstorSanPlugin) getClient(ctx context.Context) (client.BaseClientInterface, client.BaseClientInterface, error) { +func (p *OceanstorSanPlugin) getClient(ctx context.Context) (client.BaseClientInterface, + client.BaseClientInterface, error) { cli, locErr := p.mutexGetClient(ctx) var metroCli client.BaseClientInterface var rmtErr error diff --git a/csi/backend/plugin/oceanstor.go b/csi/backend/plugin/oceanstor.go index 1be174b1..cf3549a0 100644 --- a/csi/backend/plugin/oceanstor.go +++ b/csi/backend/plugin/oceanstor.go @@ -24,7 +24,6 @@ import ( "strings" xuanwuV1 "huawei-csi-driver/client/apis/xuanwu/v1" - "huawei-csi-driver/csi/app" "huawei-csi-driver/pkg/constants" pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/storage/oceanstor/client" @@ -174,11 +173,7 @@ func (p *OceanstorPlugin) updateBackendCapabilities(ctx context.Context) (map[st supportClone := utils.IsSupportFeature(features, "HyperClone") || utils.IsSupportFeature(features, "HyperCopy") supportApplicationType := p.product == "DoradoV6" - supportLabel := app.GetGlobalConfig().EnableLabel && - p.cli.GetStorageVersion() >= constants.MinVersionSupportLabel && - p.cli.IsSupportContainer(ctx) - log.AddContext(ctx).Debugf("enableLabel: %v, storageVersion: %v", app.GetGlobalConfig().EnableLabel, - p.cli.GetStorageVersion()) + log.AddContext(ctx).Debugf("storageVersion: %v", p.cli.GetStorageVersion()) capabilities := map[string]interface{}{ "SupportThin": supportThin, @@ -189,7 +184,6 @@ func (p *OceanstorPlugin) updateBackendCapabilities(ctx context.Context) (map[st "SupportApplicationType": supportApplicationType, "SupportClone": supportClone, "SupportMetroNAS": supportMetroNAS, - "SupportLabel": supportLabel, } return capabilities, nil @@ -367,6 +361,7 @@ func toLowerParams(source, target map[string]interface{}) { "accesskrb5i", "accesskrb5p", "fileSystemMode", + "metroPairSyncSpeed", } { if v, exist := source[key]; exist && v != "" { target[strings.ToLower(key)] = v @@ -415,17 +410,17 @@ func (p *OceanstorPlugin) analyzePoolsCapacity(ctx context.Context, pools []map[ var err error var freeCapacity, totalCapacity int64 if freeStr, ok := pool["USERFREECAPACITY"].(string); ok { - freeCapacity, err = strconv.ParseInt(freeStr, 10, 64) + freeCapacity, err = strconv.ParseInt(freeStr, constants.DefaultIntBase, constants.DefaultIntBitSize) } if totalStr, ok := pool["USERTOTALCAPACITY"].(string); ok { - totalCapacity, err = strconv.ParseInt(totalStr, 10, 64) + totalCapacity, err = strconv.ParseInt(totalStr, constants.DefaultIntBase, constants.DefaultIntBitSize) } if err != nil { log.AddContext(ctx).Warningf("parse capacity failed, error: %v", err) } poolCapacityMap := map[string]interface{}{ - string(xuanwuV1.FreeCapacity): freeCapacity * 512, - string(xuanwuV1.TotalCapacity): totalCapacity * 512, + string(xuanwuV1.FreeCapacity): freeCapacity * constants.AllocationUnitBytes, + string(xuanwuV1.TotalCapacity): totalCapacity * constants.AllocationUnitBytes, string(xuanwuV1.UsedCapacity): totalCapacity - freeCapacity, } if len(vStoreQuotaMap) == 0 { @@ -435,7 +430,7 @@ func (p *OceanstorPlugin) analyzePoolsCapacity(ctx context.Context, pools []map[ log.AddContext(ctx).Debugf("analyzePoolsCapacity poolName: %s, poolCapacity: %+v, vstoreQuota: %+v", name, poolCapacityMap, vStoreQuotaMap) free, ok := vStoreQuotaMap[string(xuanwuV1.FreeCapacity)].(int64) - if ok && free < freeCapacity*512 { + if ok && free < freeCapacity*constants.AllocationUnitBytes { capabilities[name] = vStoreQuotaMap } else { capabilities[name] = poolCapacityMap @@ -476,7 +471,8 @@ func (p *OceanstorPlugin) switchClient(ctx context.Context, newClient client.Bas return nil } -func (p *OceanstorPlugin) getNewClientConfig(ctx context.Context, param map[string]interface{}) (*client.NewClientConfig, error) { +func (p *OceanstorPlugin) getNewClientConfig(ctx context.Context, + param map[string]interface{}) (*client.NewClientConfig, error) { data := &client.NewClientConfig{} configUrls, exist := param["urls"].([]interface{}) if !exist || len(configUrls) <= 0 { diff --git a/csi/backend/plugin/plugin.go b/csi/backend/plugin/plugin.go index 4491c414..31a9d47f 100644 --- a/csi/backend/plugin/plugin.go +++ b/csi/backend/plugin/plugin.go @@ -25,9 +25,9 @@ import ( "huawei-csi-driver/utils" ) -// Plugin defines storage plugin interfaces -type Plugin interface { - NewPlugin() Plugin +// StoragePlugin defines storage plugin interfaces +type StoragePlugin interface { + NewPlugin() StoragePlugin Init(context.Context, map[string]interface{}, map[string]interface{}, bool) error CreateVolume(context.Context, string, map[string]interface{}) (utils.Volume, error) QueryVolume(context.Context, string, map[string]interface{}) (utils.Volume, error) @@ -39,7 +39,7 @@ type Plugin interface { UpdateBackendCapabilities(context.Context) (map[string]interface{}, map[string]interface{}, error) UpdatePoolCapabilities(context.Context, []string) (map[string]interface{}, error) - UpdateMetroRemotePlugin(context.Context, Plugin) + UpdateMetroRemotePlugin(context.Context, StoragePlugin) CreateSnapshot(context.Context, string, string) (map[string]interface{}, error) DeleteSnapshot(context.Context, string, string) error SmartXQoSQuery @@ -58,12 +58,12 @@ type Plugin interface { // SmartXQoSQuery provides Quality of Service(QoS) Query operations type SmartXQoSQuery interface { - // SupportQoSParameters checks requested QoS parameters support by Plugin + // SupportQoSParameters checks requested QoS parameters support by StoragePlugin SupportQoSParameters(ctx context.Context, qos string) error } var ( - plugins = map[string]Plugin{} + plugins = map[string]StoragePlugin{} ) const ( @@ -72,12 +72,12 @@ const ( ) // RegPlugin used to register plugin -func RegPlugin(storageType string, plugin Plugin) { +func RegPlugin(storageType string, plugin StoragePlugin) { plugins[storageType] = plugin } // GetPlugin used to get plugin by storage type -func GetPlugin(storageType string) Plugin { +func GetPlugin(storageType string) StoragePlugin { if plugin, exist := plugins[storageType]; exist { return plugin.NewPlugin() } @@ -98,7 +98,7 @@ func (p *basePlugin) DetachVolume(context.Context, string, map[string]interface{ return nil } -func (p *basePlugin) UpdateMetroRemotePlugin(context.Context, Plugin) { +func (p *basePlugin) UpdateMetroRemotePlugin(context.Context, StoragePlugin) { } // SetOnline sets the online status of plugin diff --git a/csi/backend/plugin/plugin_test.go b/csi/backend/plugin/plugin_test.go index 659da1a3..108369a9 100644 --- a/csi/backend/plugin/plugin_test.go +++ b/csi/backend/plugin/plugin_test.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ const ( var ( ctx context.Context - mockOceanstorNasPlugin Plugin + mockOceanstorNasPlugin StoragePlugin ) func TestMain(m *testing.M) { diff --git a/csi/driver/controller.go b/csi/driver/controller.go index a4292a36..2f095d27 100644 --- a/csi/driver/controller.go +++ b/csi/driver/controller.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,13 +29,12 @@ import ( "huawei-csi-driver/csi/app" "huawei-csi-driver/csi/backend/plugin" - pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) // CreateVolume used to create volume -func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { +func (d *CsiDriver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { defer utils.RecoverPanic(ctx) log.AddContext(ctx).Infof("Start to create volume %s", req.GetName()) @@ -70,7 +69,7 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) } // DeleteVolume used to delete volume -func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { +func (d *CsiDriver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { defer utils.RecoverPanic(ctx) volumeId := req.GetVolumeId() log.AddContext(ctx).Infof("Start to delete volume %s", volumeId) @@ -100,15 +99,11 @@ func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) log.AddContext(ctx).Infof("Volume %s is deleted", volumeId) - // Delete the topology after the volume is successfully deleted. - // This prevents the DeleteLabel function from being repeatedly invoked when the volume fails to be deleted. - go pkgUtils.DeletePVLabel(volumeId) - return &csi.DeleteVolumeResponse{}, nil } // ControllerExpandVolume used to controller expand volume -func (d *Driver) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) ( +func (d *CsiDriver) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) ( *csi.ControllerExpandVolumeResponse, error) { defer utils.RecoverPanic(ctx) @@ -155,7 +150,8 @@ func (d *Driver) ControllerExpandVolume(ctx context.Context, req *csi.Controller return nil, status.Error(codes.Internal, err.Error()) } - log.AddContext(ctx).Infof("Volume %s is expanded to %d, nodeExpansionRequired %t", volName, minSize, nodeExpansionRequired) + log.AddContext(ctx).Infof("Volume %s is expanded to %d, nodeExpansionRequired %t", + volName, minSize, nodeExpansionRequired) return &csi.ControllerExpandVolumeResponse{ CapacityBytes: minSize, NodeExpansionRequired: nodeExpansionRequired, @@ -163,7 +159,7 @@ func (d *Driver) ControllerExpandVolume(ctx context.Context, req *csi.Controller } // ControllerPublishVolume used to controller publish volume -func (d *Driver) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) ( +func (d *CsiDriver) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) ( *csi.ControllerPublishVolumeResponse, error) { defer utils.RecoverPanic(ctx) @@ -209,7 +205,7 @@ func (d *Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controlle } // ControllerUnpublishVolume used to controller unpublish volume -func (d *Driver) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) ( +func (d *CsiDriver) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) ( *csi.ControllerUnpublishVolumeResponse, error) { defer utils.RecoverPanic(ctx) @@ -245,24 +241,24 @@ func (d *Driver) ControllerUnpublishVolume(ctx context.Context, req *csi.Control } // ValidateVolumeCapabilities used to validate volume capabilities -func (d *Driver) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) ( +func (d *CsiDriver) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) ( *csi.ValidateVolumeCapabilitiesResponse, error) { return nil, status.Error(codes.Unimplemented, "Not implemented") } // ListVolumes used to list volumes -func (d *Driver) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) { +func (d *CsiDriver) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) { return nil, status.Error(codes.Unimplemented, "Not implemented") } // GetCapacity used to get volume capacity -func (d *Driver) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { +func (d *CsiDriver) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { return nil, status.Error(codes.Unimplemented, "Not implemented") } // ControllerGetCapabilities used to controller get capabilities -func (d *Driver) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) ( +func (d *CsiDriver) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) ( *csi.ControllerGetCapabilitiesResponse, error) { defer utils.RecoverPanic(ctx) @@ -308,7 +304,7 @@ func (d *Driver) ControllerGetCapabilities(ctx context.Context, req *csi.Control } // CreateSnapshot used to create snapshot for volume -func (d *Driver) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) ( +func (d *CsiDriver) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequest) ( *csi.CreateSnapshotResponse, error) { defer utils.RecoverPanic(ctx) @@ -350,7 +346,7 @@ func (d *Driver) CreateSnapshot(ctx context.Context, req *csi.CreateSnapshotRequ } // DeleteSnapshot used to delete snapshot -func (d *Driver) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) ( +func (d *CsiDriver) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequest) ( *csi.DeleteSnapshotResponse, error) { defer utils.RecoverPanic(ctx) @@ -379,12 +375,13 @@ func (d *Driver) DeleteSnapshot(ctx context.Context, req *csi.DeleteSnapshotRequ } // ListSnapshots used to list snapshots -func (d *Driver) ListSnapshots(ctx context.Context, req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { +func (d *CsiDriver) ListSnapshots(ctx context.Context, + req *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { return nil, status.Error(codes.Unimplemented, "") } // ControllerGetVolume is to get volume info, but unimplemented -func (d *Driver) ControllerGetVolume(ctx context.Context, req *csi.ControllerGetVolumeRequest) ( +func (d *CsiDriver) ControllerGetVolume(ctx context.Context, req *csi.ControllerGetVolumeRequest) ( *csi.ControllerGetVolumeResponse, error) { return nil, status.Error(codes.Unimplemented, "") } diff --git a/csi/driver/controller_helper.go b/csi/driver/controller_helper.go index 7ad87bdc..db5d8f60 100644 --- a/csi/driver/controller_helper.go +++ b/csi/driver/controller_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,6 @@ import ( "huawei-csi-driver/csi/backend/model" "huawei-csi-driver/csi/backend/plugin" "huawei-csi-driver/pkg/constants" - pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) @@ -50,9 +49,10 @@ const ( maxDescriptionLength = 255 - volumeTypeDTree = "dtree" - volumeTypeFileSystem = "fs" - volumeTypeLun = "lun" + volumeTypeDTree = "dtree" + volumeTypeFileSystem = "fs" + volumeTypeLun = "lun" + maxReservedSnapshotSpaceRatio = 50 ) var ( @@ -361,7 +361,7 @@ func checkReservedSnapshotSpaceRatio(ctx context.Context, parameters map[string] return errors.New(errMsg) } - if reservedSnapshotSpaceRatio < 0 || reservedSnapshotSpaceRatio > 50 { + if reservedSnapshotSpaceRatio < 0 || reservedSnapshotSpaceRatio > maxReservedSnapshotSpaceRatio { errMsg := fmt.Sprintf("reservedSnapshotSpaceRatio: [%v] must in range [0, 50], please check this "+ "parameter in storageclass.", reservedSnapshotSpaceRatioString) log.AddContext(ctx).Errorln(errMsg) @@ -446,7 +446,7 @@ func processCreateVolumeParametersAfterSelect(parameters map[string]interface{}, } // createVolume used to create a lun/filesystem in huawei storage -func (d *Driver) createVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { +func (d *CsiDriver) createVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { parameters, err := processCreateVolumeParameters(ctx, req) if err != nil { return nil, err @@ -470,15 +470,12 @@ func (d *Driver) createVolume(ctx context.Context, req *csi.CreateVolumeRequest) Volume: makeCreateVolumeResponse(ctx, req, vol, storagePoolPair.Local), } - // The topology creation result does not affect current task. - go pkgUtils.CreatePVLabel(req.GetName(), res.GetVolume().GetVolumeId()) - return res, nil } // In the volume import scenario, only the fields in the annotation are obtained. // Other information are ignored (e.g. the capacity, backend, and QoS ...). -func (d *Driver) manageVolume(ctx context.Context, req *csi.CreateVolumeRequest, volumeName, backendName string) ( +func (d *CsiDriver) manageVolume(ctx context.Context, req *csi.CreateVolumeRequest, volumeName, backendName string) ( *csi.CreateVolumeResponse, error) { log.AddContext(ctx).Infof("Start to manage Volume %s for backend %s.", volumeName, backendName) selectBackend, err := d.backendSelector.SelectBackend(ctx, helper.GetBackendName(backendName)) @@ -527,9 +524,6 @@ func (d *Driver) manageVolume(ctx context.Context, req *csi.CreateVolumeRequest, req.GetCapacityRange().GetRequiredBytes()), } - // The topology creation result does not affect current task. - go pkgUtils.CreatePVLabel(req.GetName(), res.GetVolume().GetVolumeId()) - return res, nil } diff --git a/csi/driver/controller_helper_test.go b/csi/driver/controller_helper_test.go index 0f52f3dd..ee1d9d1b 100644 --- a/csi/driver/controller_helper_test.go +++ b/csi/driver/controller_helper_test.go @@ -22,18 +22,18 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" + "huawei-csi-driver/csi/backend/model" "github.com/agiledragon/gomonkey/v2" "github.com/container-storage-interface/spec/lib/go/csi" "github.com/prashantv/gostub" - "github.com/smartystreets/goconvey/convey" "huawei-csi-driver/csi/app" cfg "huawei-csi-driver/csi/app/config" "huawei-csi-driver/csi/backend/handler" "huawei-csi-driver/csi/backend/plugin" - pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) @@ -53,32 +53,32 @@ func TestMain(m *testing.M) { } func TestCheckReservedSnapshotSpaceRatio(t *testing.T) { - convey.Convey("Normal", t, func() { + t.Run("Normal", func(t *testing.T) { param := map[string]interface{}{ "reservedSnapshotSpaceRatio": "50", } - convey.So(checkReservedSnapshotSpaceRatio(context.TODO(), param), convey.ShouldBeNil) + require.NoError(t, checkReservedSnapshotSpaceRatio(context.TODO(), param)) }) - convey.Convey("Not int", t, func() { + t.Run("Not int", func(t *testing.T) { param := map[string]interface{}{ "reservedSnapshotSpaceRatio": "20%", } - convey.So(checkReservedSnapshotSpaceRatio(context.TODO(), param), convey.ShouldBeError) + require.Error(t, checkReservedSnapshotSpaceRatio(context.TODO(), param)) }) - convey.Convey("Exceed the upper limit", t, func() { + t.Run("Exceed the upper limit", func(t *testing.T) { param := map[string]interface{}{ "reservedSnapshotSpaceRatio": "60", } - convey.So(checkReservedSnapshotSpaceRatio(context.TODO(), param), convey.ShouldBeError) + require.Error(t, checkReservedSnapshotSpaceRatio(context.TODO(), param)) }) - convey.Convey("Below the lower limit", t, func() { + t.Run("Below the lower limit", func(t *testing.T) { param := map[string]interface{}{ "reservedSnapshotSpaceRatio": "-10", } - convey.So(checkReservedSnapshotSpaceRatio(context.TODO(), param), convey.ShouldBeError) + require.Error(t, checkReservedSnapshotSpaceRatio(context.TODO(), param)) }) } @@ -100,8 +100,8 @@ func mockCreateRequest() *csi.CreateVolumeRequest { } } -func initDriver() *Driver { - return NewDriver(app.GetGlobalConfig().DriverName, +func initDriver() *CsiDriver { + return NewServer(app.GetGlobalConfig().DriverName, "csiVersion", app.GetGlobalConfig().K8sUtils, app.GetGlobalConfig().NodeName) @@ -121,9 +121,6 @@ func TestCreateVolumeWithoutBackend(t *testing.T) { driver := initDriver() req := mockCreateRequest() - s := gostub.StubFunc(&pkgUtils.CreatePVLabel) - defer s.Reset() - m := gomonkey.ApplyMethod(reflect.TypeOf(driver.backendSelector), "SelectPoolPair", func(hander *handler.BackendSelector, ctx context.Context, requestSize int64, parameters map[string]interface{}) (*model.SelectPoolPair, error) { @@ -139,8 +136,6 @@ func TestCreateVolumeWithoutBackend(t *testing.T) { func TestCreateVolume(t *testing.T) { driver := initDriver() - s := gostub.StubFunc(&pkgUtils.CreatePVLabel) - defer s.Reset() m := gomonkey.ApplyMethod(reflect.TypeOf(driver.backendSelector), "SelectPoolPair", func(hander *handler.BackendSelector, ctx context.Context, requestSize int64, parameters map[string]interface{}) (*model.SelectPoolPair, error) { @@ -180,9 +175,6 @@ func TestImportVolumeWithoutBackend(t *testing.T) { }) defer m.Reset() - s := gostub.StubFunc(&pkgUtils.CreatePVLabel) - defer s.Reset() - _, err := driver.manageVolume(context.TODO(), req, "fake-nfs", "fake-backend") if err == nil { t.Error("test import without backend failed") @@ -193,8 +185,6 @@ func TestImportVolume(t *testing.T) { plg := plugin.GetPlugin("oceanstor-nas") localPool := initPool("local-pool") - s := gostub.StubFunc(&pkgUtils.CreatePVLabel) - defer s.Reset() driver := initDriver() m := gomonkey.ApplyMethod(reflect.TypeOf(driver.backendSelector), "SelectBackend", func(hander *handler.BackendSelector, ctx context.Context, backendName string) (*model.Backend, error) { diff --git a/csi/driver/driver.go b/csi/driver/driver.go index 1e0bad77..95d61143 100644 --- a/csi/driver/driver.go +++ b/csi/driver/driver.go @@ -23,8 +23,8 @@ import ( "huawei-csi-driver/utils/k8sutils" ) -// Driver defines csi driver -type Driver struct { +// CsiDriver defines csi driver +type CsiDriver struct { name string version string k8sUtils k8sutils.Interface @@ -32,9 +32,9 @@ type Driver struct { backendSelector handler.BackendSelectInterface } -// NewDriver used to inits a new driver -func NewDriver(name, version string, k8sUtils k8sutils.Interface, nodeName string) *Driver { - return &Driver{ +// NewServer used to inits a new driver +func NewServer(name, version string, k8sUtils k8sutils.Interface, nodeName string) *CsiDriver { + return &CsiDriver{ name: name, version: version, k8sUtils: k8sUtils, diff --git a/csi/driver/identity.go b/csi/driver/identity.go index c37b11d3..822d172b 100644 --- a/csi/driver/identity.go +++ b/csi/driver/identity.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,13 +19,14 @@ package driver import ( "context" - "huawei-csi-driver/utils/log" - "github.com/container-storage-interface/spec/lib/go/csi" + + "huawei-csi-driver/utils/log" ) // GetPluginInfo used to get plugin info -func (d *Driver) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { +func (d *CsiDriver) GetPluginInfo(ctx context.Context, + req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { log.AddContext(ctx).Infof("Get plugin info %v", *d) return &csi.GetPluginInfoResponse{ Name: d.name, @@ -34,7 +35,8 @@ func (d *Driver) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoReques } // GetPluginCapabilities used to get plugin capabilities -func (d *Driver) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) { +func (d *CsiDriver) GetPluginCapabilities(ctx context.Context, + req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) { log.AddContext(ctx).Infof("Get plugin capabilities of %v", *d) return &csi.GetPluginCapabilitiesResponse{ @@ -58,7 +60,7 @@ func (d *Driver) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCa } // Probe used to health check -func (d *Driver) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) { +func (d *CsiDriver) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) { log.AddContext(ctx).Debugf("Probe plugin %v", *d) return &csi.ProbeResponse{}, nil } diff --git a/csi/driver/node.go b/csi/driver/node.go index 991091aa..7f3cfdcf 100644 --- a/csi/driver/node.go +++ b/csi/driver/node.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,8 +36,11 @@ import ( "huawei-csi-driver/utils/log" ) +const checkSymlinkPathTimeout = 10 * time.Second + // NodeStageVolume used to stage volume -func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { +func (d *CsiDriver) NodeStageVolume(ctx context.Context, + req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { defer utils.RecoverPanic(ctx) volumeId := req.GetVolumeId() @@ -61,7 +64,8 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe } // NodeUnstageVolume used to unstage volume -func (d *Driver) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { +func (d *CsiDriver) NodeUnstageVolume(ctx context.Context, + req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { defer utils.RecoverPanic(ctx) volumeId := req.GetVolumeId() targetPath := req.GetStagingTargetPath() @@ -86,7 +90,7 @@ func (d *Driver) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolu } // NodePublishVolume used to node publish volume -func (d *Driver) NodePublishVolume(ctx context.Context, +func (d *CsiDriver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { defer utils.RecoverPanic(ctx) volumeId := req.GetVolumeId() @@ -105,14 +109,12 @@ func (d *Driver) NodePublishVolume(ctx context.Context, } } - go nodeAddLabel(utils.NewContextWithRequestID(), volumeId, targetPath) - log.AddContext(ctx).Infof("Volume %s is node published from %s", volumeId, targetPath) return &csi.NodePublishVolumeResponse{}, nil } // NodeUnpublishVolume used to node unpublish volume -func (d *Driver) NodeUnpublishVolume(ctx context.Context, +func (d *CsiDriver) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { defer utils.RecoverPanic(ctx) @@ -137,7 +139,7 @@ func (d *Driver) NodeUnpublishVolume(ctx context.Context, } } } else { - symLink, err := utils.IsPathSymlinkWithTimeout(targetPath, 10*time.Second) + symLink, err := utils.IsPathSymlinkWithTimeout(targetPath, checkSymlinkPathTimeout) if err != nil { log.AddContext(ctx).Errorf("Failed to Access path %s, error: %v", targetPath, err) return nil, status.Error(codes.Internal, err.Error()) @@ -152,14 +154,12 @@ func (d *Driver) NodeUnpublishVolume(ctx context.Context, } } - go nodeDeleteLabel(utils.NewContextWithRequestID(), volumeId, targetPath) - log.AddContext(ctx).Infof("Volume %s is node unpublished from %s", volumeId, targetPath) return &csi.NodeUnpublishVolumeResponse{}, nil } // NodeGetInfo used to get node info -func (d *Driver) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { +func (d *CsiDriver) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { defer utils.RecoverPanic(ctx) hostname, err := utils.GetHostName(ctx) if err != nil { @@ -202,7 +202,8 @@ func (d *Driver) NodeGetInfo(ctx context.Context, req *csi.NodeGetInfoRequest) ( } // NodeGetCapabilities used to get node capabilities -func (d *Driver) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { +func (d *CsiDriver) NodeGetCapabilities(ctx context.Context, + req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { defer utils.RecoverPanic(ctx) return &csi.NodeGetCapabilitiesResponse{ Capabilities: []*csi.NodeServiceCapability{ @@ -232,7 +233,8 @@ func (d *Driver) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabi } // NodeGetVolumeStats used to get node volume status -func (d *Driver) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) { +func (d *CsiDriver) NodeGetVolumeStats(ctx context.Context, + req *csi.NodeGetVolumeStatsRequest) (*csi.NodeGetVolumeStatsResponse, error) { defer utils.RecoverPanic(ctx) volumeID := req.GetVolumeId() if len(volumeID) == 0 { @@ -317,7 +319,7 @@ func (d *Driver) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVolumeS } // NodeExpandVolume used to node expand volume -func (d *Driver) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) ( +func (d *CsiDriver) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) ( *csi.NodeExpandVolumeResponse, error) { defer utils.RecoverPanic(ctx) diff --git a/csi/garbage_collector.go b/csi/garbage_collector.go index 613e0afe..7af85658 100644 --- a/csi/garbage_collector.go +++ b/csi/garbage_collector.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,8 @@ const ( // deviceDirPath is a relative path inside kubelet root directory relativeDevicePath = "/kubelet/plugins/kubernetes.io/csi/volumeDevices/*/data/vol_data.json" // in case of block,the index of the last occurrence of the specified pv name - // For example,the path is "/var/lib/kubelet/plugins/kubernetes.io/csi/volumeDevices/pvc-123/data/vol_data.json", we get pvc-123 + // For example,the path is + // "/var/lib/kubelet/plugins/kubernetes.io/csi/volumeDevices/pvc-123/data/vol_data.json", we get pvc-123 deviceLastIndex = 3 ) diff --git a/csi/main.go b/csi/main.go index 5a8a0c8f..b9963dcf 100644 --- a/csi/main.go +++ b/csi/main.go @@ -38,7 +38,6 @@ import ( "huawei-csi-driver/csi/driver" "huawei-csi-driver/csi/provider" "huawei-csi-driver/lib/drcsi" - labelLock "huawei-csi-driver/pkg/utils/label_lock" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" "huawei-csi-driver/utils/notify" @@ -50,7 +49,7 @@ const ( controllerLogFile = "huawei-csi-controller" nodeLogFile = "huawei-csi-node" - csiVersion = "4.4.0" + csiVersion = "4.5.0" endpointDirPerm = 0755 ) @@ -102,9 +101,6 @@ func runCSIController(ctx context.Context) { // register the kahu community DRCSI service go registerDRCSIServer() - // create and refresh lock for rt - go labelLock.InitCmLock(ctx, labelLock.RTLockConfigMap) - // register the K8S community CSI service registerCSIServer() } @@ -147,7 +143,7 @@ func main() { } // Init logger - err := log.InitLogging(&log.LoggingRequest{ + err := log.InitLogging(&log.Config{ LogName: getLogFileName(), LogFileSize: app.GetGlobalConfig().LogFileSize, LoggingModule: app.GetGlobalConfig().LoggingModule, @@ -184,7 +180,7 @@ func registerDRCSIServer() { } func registerCSIServer() { - d := driver.NewDriver(app.GetGlobalConfig().DriverName, + d := driver.NewServer(app.GetGlobalConfig().DriverName, csiVersion, app.GetGlobalConfig().K8sUtils, app.GetGlobalConfig().NodeName) @@ -217,7 +213,7 @@ func listenEndpoint(endpoint string) net.Listener { return listener } -func registerServer(listener net.Listener, d *driver.Driver) { +func registerServer(listener net.Listener, d *driver.CsiDriver) { opts := []grpc.ServerOption{ grpc.UnaryInterceptor(log.EnsureGRPCContext), } diff --git a/csi/manage/manager_helper.go b/csi/manage/manager_helper.go index ca6abdf1..7017db81 100644 --- a/csi/manage/manager_helper.go +++ b/csi/manage/manager_helper.go @@ -28,7 +28,7 @@ import ( "github.com/container-storage-interface/spec/lib/go/csi" "huawei-csi-driver/connector" - _ "huawei-csi-driver/connector/nfs_plus" + _ "huawei-csi-driver/connector/nfsplus" "huawei-csi-driver/csi/app" "huawei-csi-driver/csi/backend" "huawei-csi-driver/csi/backend/plugin" @@ -116,7 +116,7 @@ func WithPortals(publishContext map[string]string, protocol string, portals, met } // WithConnector build connector for the request parameters -func WithConnector(conn connector.Connector) BuildParameterOption { +func WithConnector(conn connector.VolumeConnector) BuildParameterOption { return func(parameters map[string]interface{}) error { parameters["connector"] = conn return nil @@ -231,7 +231,7 @@ func Unmount(ctx context.Context, targetPath string) error { } // NewManager build a manager instance, such as NasManager, SanManager -func NewManager(ctx context.Context, backendName string) (Manager, error) { +func NewManager(ctx context.Context, backendName string) (VolumeManager, error) { backend, err := GetBackendConfig(ctx, backendName) if err != nil { log.AddContext(ctx).Errorf("nas manager get backend failed, backendName: %s err: %v", backendName, err) @@ -249,7 +249,7 @@ func NewManager(ctx context.Context, backendName string) (Manager, error) { return nil, utils.Errorf(ctx, "portals can not be blank when protocol is %s", plugin.ProtocolNfsPlus) } return NewNasManager(ctx, backend.protocol, backend.dTreeParentName, backend.portals, backend.metroPortals) - case plugin.PROTOCOL_DPC: + case plugin.ProtocolDpc: return NewNasManager(ctx, backend.protocol, backend.dTreeParentName, []string{}, []string{}) default: return NewSanManager(ctx, backend.protocol) @@ -401,10 +401,10 @@ func PublishFilesystem(ctx context.Context, req *csi.NodePublishVolumeRequest) e return nil } -func getConnectorByProtocol(ctx context.Context, protocol string) connector.Connector { - return map[string]connector.Connector{ +func getConnectorByProtocol(ctx context.Context, protocol string) connector.VolumeConnector { + return map[string]connector.VolumeConnector{ plugin.ProtocolNfs: connector.GetConnector(ctx, connector.NFSDriver), - plugin.PROTOCOL_DPC: connector.GetConnector(ctx, connector.NFSDriver), + plugin.ProtocolDpc: connector.GetConnector(ctx, connector.NFSDriver), plugin.ProtocolNfsPlus: connector.GetConnector(ctx, connector.NFSPlusDriver), }[protocol] } diff --git a/csi/manage/manager_helper_test.go b/csi/manage/manager_helper_test.go index 566e2266..4cceb56d 100644 --- a/csi/manage/manager_helper_test.go +++ b/csi/manage/manager_helper_test.go @@ -43,7 +43,7 @@ type testCaseStructForNewManager struct { name string protocol string backendName string - want Manager + want VolumeManager wantErr bool } diff --git a/csi/manage/nas_manager.go b/csi/manage/nas_manager.go index 755205c5..49426008 100644 --- a/csi/manage/nas_manager.go +++ b/csi/manage/nas_manager.go @@ -28,18 +28,18 @@ import ( "huawei-csi-driver/utils/log" ) -// NasManager implements Manager interface +// NasManager implements VolumeManager interface type NasManager struct { protocol string portals []string metroPortals []string dTreeParentName string - Conn connector.Connector + Conn connector.VolumeConnector } // NewNasManager build a nas manager instance according to the protocol -func NewNasManager(ctx context.Context, protocol, dTreeParentName string, portals, metroPortals []string) (Manager, - error) { +func NewNasManager(ctx context.Context, + protocol, dTreeParentName string, portals, metroPortals []string) (VolumeManager, error) { return &NasManager{ protocol: protocol, portals: portals, @@ -73,7 +73,7 @@ func (m *NasManager) StageVolume(ctx context.Context, req *csi.NodeStageVolumeRe var sourcePath string switch m.protocol { - case plugin.PROTOCOL_DPC: + case plugin.ProtocolDpc: sourcePath = "/" + volumeName case plugin.ProtocolNfs, plugin.ProtocolNfsPlus: sourcePath = m.portals[0] + ":/" + volumeName diff --git a/csi/manage/san_manager.go b/csi/manage/san_manager.go index d7e6c7bc..45cdfb7b 100644 --- a/csi/manage/san_manager.go +++ b/csi/manage/san_manager.go @@ -24,19 +24,19 @@ import ( "huawei-csi-driver/connector" "huawei-csi-driver/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" ) -// SanManager implements Manager interface +// SanManager implements VolumeManager interface type SanManager struct { - Conn connector.Connector + Conn connector.VolumeConnector protocol string } // NewSanManager build a san manager instance according to the protocol -func NewSanManager(ctx context.Context, protocol string) (Manager, error) { - var conn connector.Connector +func NewSanManager(ctx context.Context, protocol string) (VolumeManager, error) { + var conn connector.VolumeConnector switch protocol { case "iscsi": conn = connector.GetConnector(ctx, connector.ISCSIDriver) @@ -74,7 +74,7 @@ func (m *SanManager) StageVolume(ctx context.Context, req *csi.NodeStageVolumeRe return err } - tasks := taskflow.NewTaskFlow(ctx, "StageVolume"). + tasks := flow.NewTaskFlow(ctx, "StageVolume"). AddTaskWithOutRevert(clearResidualPathWithWwn). AddTaskWithOutRevert(clearResidualPathWithLunId). AddTaskWithOutRevert(connectVolume) @@ -217,7 +217,12 @@ func clearResidualPathWithWwn(ctx context.Context, parameters map[string]interfa return err } - return connector.ClearResidualPath(ctx, wwn, parameters["volumeMode"]) + publishInfo, exist := parameters["publishInfo"].(*ControllerPublishInfo) + if !exist { + return errors.New("build multiPathType failed, caused by publishInfo is not exist") + } + + return connector.ClearResidualPath(ctx, wwn, parameters["volumeMode"], publishInfo.MultiPathType) } func connectVolume(ctx context.Context, parameters map[string]interface{}) error { @@ -228,7 +233,7 @@ func connectVolume(ctx context.Context, parameters map[string]interface{}) error } connectionParams := publishInfo.ReflectToMap() - conn, exist := parameters["connector"].(connector.Connector) + conn, exist := parameters["connector"].(connector.VolumeConnector) if !exist { return errors.New("connector doesn't exist while connect volume") } diff --git a/csi/manage/san_manager_test.go b/csi/manage/san_manager_test.go index 12d3b330..a2aa7232 100644 --- a/csi/manage/san_manager_test.go +++ b/csi/manage/san_manager_test.go @@ -41,7 +41,7 @@ func TestSanManagerStageFileSystemVolume(t *testing.T) { tests := []struct { name string manager *SanManager - connectVolumeFunc func(patch *gomonkey.Patches, conn connector.Connector) + connectVolumeFunc func(patch *gomonkey.Patches, conn connector.VolumeConnector) wantErr bool }{ { @@ -139,9 +139,9 @@ func mockClearResidualPath(patch *gomonkey.Patches, protocol string) { }) } -func mockConnectIscsiVolume(patch *gomonkey.Patches, conn connector.Connector) { +func mockConnectIscsiVolume(patch *gomonkey.Patches, conn connector.VolumeConnector) { patch.ApplyMethod(reflect.TypeOf(conn), "ConnectVolume", - func(_ *iscsi.ISCSI, ctx context.Context, params map[string]interface{}) (string, error) { + func(_ *iscsi.Connector, ctx context.Context, params map[string]interface{}) (string, error) { want := map[string]interface{}{ "tgtPortals": []string{"mock_tgt_portal_1"}, "tgtIQNs": []string{"mock_tgt_iqn_1"}, @@ -160,9 +160,9 @@ func mockConnectIscsiVolume(patch *gomonkey.Patches, conn connector.Connector) { }) } -func mockConnectFcVolume(patch *gomonkey.Patches, conn connector.Connector) { +func mockConnectFcVolume(patch *gomonkey.Patches, conn connector.VolumeConnector) { patch.ApplyMethod(reflect.TypeOf(conn), "ConnectVolume", - func(_ *fibrechannel.FibreChannel, ctx context.Context, params map[string]interface{}) (string, error) { + func(_ *fibrechannel.Connector, ctx context.Context, params map[string]interface{}) (string, error) { want := map[string]interface{}{ "tgtWWNs": []string{"mock_wwn_1"}, "tgtHostLUNs": []string{"mock_host_lun_1"}, @@ -180,9 +180,9 @@ func mockConnectFcVolume(patch *gomonkey.Patches, conn connector.Connector) { }) } -func mockConnectRoceVolume(patch *gomonkey.Patches, conn connector.Connector) { +func mockConnectRoceVolume(patch *gomonkey.Patches, conn connector.VolumeConnector) { patch.ApplyMethod(reflect.TypeOf(conn), "ConnectVolume", - func(_ *roce.RoCE, ctx context.Context, params map[string]interface{}) (string, error) { + func(_ *roce.Connector, ctx context.Context, params map[string]interface{}) (string, error) { want := map[string]interface{}{ "tgtPortals": []string{"mock_tgt_portal_1"}, "tgtLunGuid": "mock_lun_guid_1", @@ -199,7 +199,7 @@ func mockConnectRoceVolume(patch *gomonkey.Patches, conn connector.Connector) { }) } -func mockConnectFcNvmeVolume(patch *gomonkey.Patches, conn connector.Connector) { +func mockConnectFcNvmeVolume(patch *gomonkey.Patches, conn connector.VolumeConnector) { patch.ApplyMethod(reflect.TypeOf(conn), "ConnectVolume", func(_ *nvme.FCNVMe, ctx context.Context, params map[string]interface{}) (string, error) { want := map[string]interface{}{ diff --git a/csi/manage/types.go b/csi/manage/types.go index 960dfb2c..866c018a 100644 --- a/csi/manage/types.go +++ b/csi/manage/types.go @@ -24,8 +24,8 @@ import ( "huawei-csi-driver/connector/nvme" ) -// Manager defines the operations which storage manager should implement -type Manager interface { +// VolumeManager defines the operations which storage manager should implement +type VolumeManager interface { StageVolume(context.Context, *csi.NodeStageVolumeRequest) error UnStageVolume(context.Context, *csi.NodeUnstageVolumeRequest) error ExpandVolume(context.Context, *csi.NodeExpandVolumeRequest) error diff --git a/csi/provider/backend.go b/csi/provider/backend.go index 5b24ae1e..ef7fcd85 100644 --- a/csi/provider/backend.go +++ b/csi/provider/backend.go @@ -31,7 +31,7 @@ import ( ) // AddStorageBackend used to add storage backend, and return the backend ID -func (p *Provider) AddStorageBackend(ctx context.Context, req *drcsi.AddStorageBackendRequest) ( +func (p *StorageProvider) AddStorageBackend(ctx context.Context, req *drcsi.AddStorageBackendRequest) ( *drcsi.AddStorageBackendResponse, error) { log.AddContext(ctx).Infof("Start to add storage backend %s.", req.Name) @@ -49,7 +49,7 @@ func (p *Provider) AddStorageBackend(ctx context.Context, req *drcsi.AddStorageB } // RemoveStorageBackend remove the backend id in current provider -func (p *Provider) RemoveStorageBackend(ctx context.Context, req *drcsi.RemoveStorageBackendRequest) ( +func (p *StorageProvider) RemoveStorageBackend(ctx context.Context, req *drcsi.RemoveStorageBackendRequest) ( *drcsi.RemoveStorageBackendResponse, error) { log.AddContext(ctx).Infof("Start to remove storage backend %s.", req.BackendId) @@ -68,7 +68,7 @@ func (p *Provider) RemoveStorageBackend(ctx context.Context, req *drcsi.RemoveSt } // UpdateStorageBackend update the backend within backend id -func (p *Provider) UpdateStorageBackend(ctx context.Context, req *drcsi.UpdateStorageBackendRequest) ( +func (p *StorageProvider) UpdateStorageBackend(ctx context.Context, req *drcsi.UpdateStorageBackendRequest) ( *drcsi.UpdateStorageBackendResponse, error) { // In the current version, the CSI supports only password change, which is verified through webhook. @@ -100,7 +100,7 @@ func (p *Provider) UpdateStorageBackend(ctx context.Context, req *drcsi.UpdateSt } // GetBackendStats used to update the storage backend status -func (p *Provider) GetBackendStats(ctx context.Context, req *drcsi.GetBackendStatsRequest) ( +func (p *StorageProvider) GetBackendStats(ctx context.Context, req *drcsi.GetBackendStatsRequest) ( *drcsi.GetBackendStatsResponse, error) { log.AddContext(ctx).Debugf("Start to get storage backend %s status.", req.BackendId) @@ -120,7 +120,7 @@ func (p *Provider) GetBackendStats(ctx context.Context, req *drcsi.GetBackendSta return nil, errors.New(msg) } - details, err := p.storageService.GetBackendDetails(ctx, backendName) + details, err := p.storageService.GetBackendDetails(ctx, backendName, req.Name) if err != nil { log.AddContext(ctx).Errorf("get backend details failed, error: %v", err) return nil, err @@ -140,7 +140,7 @@ func (p *Provider) GetBackendStats(ctx context.Context, req *drcsi.GetBackendSta return response, nil } -func (p *Provider) registerOrUpdateOneBackend(ctx context.Context, name, backendId string, +func (p *StorageProvider) registerOrUpdateOneBackend(ctx context.Context, name, backendId string, response *drcsi.GetBackendStatsResponse) { var err error var sbct *v1.StorageBackendContent diff --git a/csi/provider/identity.go b/csi/provider/identity.go index a933225b..8bda1d86 100644 --- a/csi/provider/identity.go +++ b/csi/provider/identity.go @@ -25,7 +25,7 @@ import ( ) // GetProviderInfo is used to get provider info -func (p *Provider) GetProviderInfo(ctx context.Context, req *drcsi.GetProviderInfoRequest) ( +func (p *StorageProvider) GetProviderInfo(ctx context.Context, req *drcsi.GetProviderInfoRequest) ( *drcsi.GetProviderInfoResponse, error) { log.AddContext(ctx).Infof("Get provider info %v", *p) @@ -36,7 +36,7 @@ func (p *Provider) GetProviderInfo(ctx context.Context, req *drcsi.GetProviderIn } // GetProviderCapabilities is used to get provider capabilities -func (p *Provider) GetProviderCapabilities(ctx context.Context, req *drcsi.GetProviderCapabilitiesRequest) ( +func (p *StorageProvider) GetProviderCapabilities(ctx context.Context, req *drcsi.GetProviderCapabilitiesRequest) ( *drcsi.GetProviderCapabilitiesResponse, error) { log.AddContext(ctx).Infof("Get plugin capabilities of %v", *p) @@ -54,7 +54,7 @@ func (p *Provider) GetProviderCapabilities(ctx context.Context, req *drcsi.GetPr } // Probe is used to probe provider -func (p *Provider) Probe(ctx context.Context, in *drcsi.ProbeRequest) (*drcsi.ProbeResponse, error) { +func (p *StorageProvider) Probe(ctx context.Context, in *drcsi.ProbeRequest) (*drcsi.ProbeResponse, error) { log.AddContext(ctx).Infof("Probe invoked of %v, request: %v", *p, in) return &drcsi.ProbeResponse{}, nil } diff --git a/csi/provider/provider.go b/csi/provider/provider.go index d6a18628..c0c1a1e9 100644 --- a/csi/provider/provider.go +++ b/csi/provider/provider.go @@ -19,8 +19,8 @@ package provider import "huawei-csi-driver/csi/backend/handler" -// Provider is for storage provider -type Provider struct { +// StorageProvider is for storage provider +type StorageProvider struct { name string version string storageService handler.StorageServiceInterface @@ -31,8 +31,8 @@ type Provider struct { } // NewProvider is used to create storage provider -func NewProvider(name, version string) *Provider { - return &Provider{ +func NewProvider(name, version string) *StorageProvider { + return &StorageProvider{ name: name, version: version, storageService: handler.NewStorageHandler(), diff --git a/csi/provider/volume.go b/csi/provider/volume.go index fa01d329..a7175e6c 100644 --- a/csi/provider/volume.go +++ b/csi/provider/volume.go @@ -34,7 +34,7 @@ const ( ) // ModifyVolume is used to modify volume attribute -func (p *Provider) ModifyVolume(ctx context.Context, req *drcsi.ModifyVolumeRequest) ( +func (p *StorageProvider) ModifyVolume(ctx context.Context, req *drcsi.ModifyVolumeRequest) ( *drcsi.ModifyVolumeResponse, error) { defer utils.RecoverPanic(ctx) @@ -49,7 +49,7 @@ func (p *Provider) ModifyVolume(ctx context.Context, req *drcsi.ModifyVolumeRequ return &drcsi.ModifyVolumeResponse{}, nil } -func (p *Provider) modifyHyperMetro(ctx context.Context, req *drcsi.ModifyVolumeRequest) ( +func (p *StorageProvider) modifyHyperMetro(ctx context.Context, req *drcsi.ModifyVolumeRequest) ( *drcsi.ModifyVolumeResponse, error) { // Determine whether the operation is a conversion between a local volume and a HyperMetro volume. @@ -84,7 +84,7 @@ func (p *Provider) modifyHyperMetro(ctx context.Context, req *drcsi.ModifyVolume return nil, errors.New(errMsg) } - params := pkgUtils.CombineMap(req.StorageClassParameters, req.MutableParameters) + params := pkgUtils.CombineMap(req.MutableParameters, req.StorageClassParameters) remotePool, err := p.backendSelector.SelectRemotePool(ctx, thinVolumeRequestSize, backendName, pkgUtils.ConvertMapString2MapInterface(params)) if err != nil { @@ -94,9 +94,9 @@ func (p *Provider) modifyHyperMetro(ctx context.Context, req *drcsi.ModifyVolume } if remotePool != nil { - req.StorageClassParameters["remoteStoragePool"] = remotePool.Name + params["remoteStoragePool"] = remotePool.Name } - err = bk.Plugin.ModifyVolume(ctx, req.VolumeId, modifyType, req.StorageClassParameters) + err = bk.Plugin.ModifyVolume(ctx, req.VolumeId, modifyType, params) if err != nil { errMsg := fmt.Sprintf("modify volume failed, volume name: %s, modify type: %v, error: %v", volumeName, modifyType, err) diff --git a/csi/provider/volume_test.go b/csi/provider/volume_test.go index 9724ea84..a78496e7 100644 --- a/csi/provider/volume_test.go +++ b/csi/provider/volume_test.go @@ -85,6 +85,7 @@ func TestModifyVolume_HyperMetroTrue(t *testing.T) { func(*handler.BackendSelector, context.Context, int64, string, map[string]interface{}) (*model.StoragePool, error) { return &model.StoragePool{Name: "remotePoolName"}, nil }) + m.ApplyPrivateMethod(nasPlugin, "canModify", func() error { return nil }) m.ApplyMethod(reflect.TypeOf(nasPlugin), "GetLocal2HyperMetroParameters", func(p *plugin.OceanstorNasPlugin, ctx context.Context, VolumeId string, parameters map[string]string) ( map[string]interface{}, error) { diff --git a/docs/.keepdir b/docs/.keepdir deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.4.0 User Guide 01.pdf b/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.4.0 User Guide 01.pdf deleted file mode 100644 index f0f203af..00000000 Binary files a/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.4.0 User Guide 01.pdf and /dev/null differ diff --git "a/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.4.0 \347\224\250\346\210\267\346\214\207\345\215\227 01.pdf" "b/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.4.0 \347\224\250\346\210\267\346\214\207\345\215\227 01.pdf" deleted file mode 100644 index 6f33a6e3..00000000 Binary files "a/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.4.0 \347\224\250\346\210\267\346\214\207\345\215\227 01.pdf" and /dev/null differ diff --git a/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.5.0 User Guide 01.pdf b/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.5.0 User Guide 01.pdf new file mode 100644 index 00000000..6ad0ab22 Binary files /dev/null and b/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.5.0 User Guide 01.pdf differ diff --git "a/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.5.0 \347\224\250\346\210\267\346\214\207\345\215\227 01.pdf" "b/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.5.0 \347\224\250\346\210\267\346\214\207\345\215\227 01.pdf" new file mode 100644 index 00000000..bde09445 Binary files /dev/null and "b/docs/eSDK Huawei Storage Kubernetes CSI Plugins V4.5.0 \347\224\250\346\210\267\346\214\207\345\215\227 01.pdf" differ diff --git a/go.mod b/go.mod index f1c8d600..4394b63e 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,25 @@ module huawei-csi-driver -go 1.20 +go 1.22 require ( - bou.ke/monkey v1.0.2 github.com/agiledragon/gomonkey/v2 v2.9.0 - github.com/container-storage-interface/spec v1.6.0 - github.com/ghodss/yaml v1.0.0 + github.com/container-storage-interface/spec v1.8.0 github.com/golang/mock v1.4.4 github.com/golang/protobuf v1.5.3 github.com/kubernetes-csi/csi-lib-utils v0.11.0 github.com/prashantv/gostub v1.1.0 - github.com/sirupsen/logrus v1.8.0 - github.com/smartystreets/goconvey v1.7.2 - github.com/spf13/cobra v1.4.0 + github.com/sirupsen/logrus v1.8.2 + github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 - golang.org/x/sys v0.17.0 + golang.org/x/sys v0.24.0 google.golang.org/grpc v1.57.2 google.golang.org/protobuf v1.32.0 - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.2 k8s.io/apimachinery v0.29.2 k8s.io/client-go v0.29.2 k8s.io/code-generator v0.27.6 - k8s.io/utils v0.0.0-20230726121419-3b25d923346b ) require ( @@ -44,13 +40,10 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect github.com/imdario/mergo v0.3.6 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/jtolds/gls v4.20.0+incompatible // indirect - github.com/magefile/mage v1.10.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -62,7 +55,6 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/smartystreets/assertions v1.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.21.0 // indirect @@ -74,11 +66,12 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/component-base v0.22.0 // indirect k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/helm/esdk/Chart.yaml b/helm/esdk/Chart.yaml index 26fae58b..1139424c 100644 --- a/helm/esdk/Chart.yaml +++ b/helm/esdk/Chart.yaml @@ -15,14 +15,14 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 4.4.0 +version: 4.5.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. # It is strongly recommended not to modify this parameter -appVersion: "4.4.0" +appVersion: "4.5.0" home: https://github.com/Huawei/eSDK_K8S_Plugin sources: diff --git a/helm/esdk/templates/huawei-csi-controller.yaml b/helm/esdk/templates/huawei-csi-controller.yaml index 93ea347c..678dbe04 100644 --- a/helm/esdk/templates/huawei-csi-controller.yaml +++ b/helm/esdk/templates/huawei-csi-controller.yaml @@ -429,9 +429,6 @@ rules: - apiGroups: [ "" ] resources: [ "secrets" ] verbs: [ "get" ] - - apiGroups: [ "xuanwu.huawei.io" ] - resources: [ "resourcetopologies" ] - verbs: [ "create", "get", "update", "delete" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -763,7 +760,6 @@ spec: - "--logging-module={{ .Values.csiDriver.controllerLogging.module }}" - "--log-level={{ .Values.csiDriver.controllerLogging.level }}" - "--volume-name-prefix={{ default "pvc" (.Values.controller).volumeNamePrefix }}" - - "--enable-label={{ .Values.csiDriver.enableLabel }}" {{ if eq .Values.csiDriver.controllerLogging.module "file" }} - "--log-file-dir={{ .Values.csiDriver.controllerLogging.fileDir }}" - "--log-file-size={{ .Values.csiDriver.controllerLogging.fileSize }}" diff --git a/helm/esdk/templates/huawei-csi-node.yaml b/helm/esdk/templates/huawei-csi-node.yaml index 5f5c1e23..ccb1edf9 100644 --- a/helm/esdk/templates/huawei-csi-node.yaml +++ b/helm/esdk/templates/huawei-csi-node.yaml @@ -56,9 +56,6 @@ rules: - apiGroups: [ "xuanwu.huawei.io" ] resources: [ "storagebackendclaims","storagebackendcontents" ] verbs: [ "get" ] - - apiGroups: [ "xuanwu.huawei.io" ] - resources: [ "resourcetopologies" ] - verbs: [ "create", "get", "update", "delete" ] - apiGroups: [ "" ] resources: [ "pods" ] verbs: [ "list","get" ] diff --git a/helm/esdk/values.yaml b/helm/esdk/values.yaml index 3076b934..a9419130 100644 --- a/helm/esdk/values.yaml +++ b/helm/esdk/values.yaml @@ -1,9 +1,9 @@ images: # Images provided by Huawei - huaweiCSIService: huawei-csi:4.4.0 - storageBackendSidecar: storage-backend-sidecar:4.4.0 - storageBackendController: storage-backend-controller:4.4.0 - huaweiCSIExtender: huawei-csi-extender:4.4.0 + huaweiCSIService: huawei-csi:4.5.0 + storageBackendSidecar: storage-backend-sidecar:4.5.0 + storageBackendController: storage-backend-controller:4.5.0 + huaweiCSIExtender: huawei-csi-extender:4.5.0 # CSI-related sidecar images provided by the Kubernetes community. # These must match the appropriate Kubernetes version. @@ -217,8 +217,6 @@ csiDriver: allPathOnline: false # Interval for updating backend capabilities. support 60~600 backendUpdateInterval: 60 - # label enable - enableLabel: false # Huawei-csi-controller log configuration controllerLogging: # Log record type, support [file, console] diff --git a/lib/drcsi/connection/connection.go b/lib/drcsi/connection/connection.go index 8c468511..9ea6eb4e 100644 --- a/lib/drcsi/connection/connection.go +++ b/lib/drcsi/connection/connection.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -27,7 +27,8 @@ import ( // Connect opens insecure gRPC connection to a CSI driver. Address must be either absolute path to UNIX domain socket // file or have format '://', following gRPC name resolution mechanism at // https://github.com/grpc/grpc/blob/master/doc/naming.md. -func Connect(ctx context.Context, drCSIAddress string, metricsManager metrics.CSIMetricsManager) (conn *grpc.ClientConn, err error) { +func Connect(ctx context.Context, + drCSIAddress string, metricsManager metrics.CSIMetricsManager) (conn *grpc.ClientConn, err error) { var m sync.Mutex var canceled bool ready := make(chan bool) diff --git a/manual/esdk/deploy/huawei-csi-controller-extender.yaml b/manual/esdk/deploy/huawei-csi-controller-extender.yaml index 5e107e87..2a9c48f0 100644 --- a/manual/esdk/deploy/huawei-csi-controller-extender.yaml +++ b/manual/esdk/deploy/huawei-csi-controller-extender.yaml @@ -416,9 +416,6 @@ rules: - apiGroups: [ "" ] resources: [ "secrets" ] verbs: [ "get" ] - - apiGroups: [ "xuanwu.huawei.io" ] - resources: [ "resourcetopologies" ] - verbs: [ "create", "get", "update", "delete" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -571,7 +568,7 @@ spec: cpu: 50m memory: 128Mi - name: storage-backend-controller - image: storage-backend-controller:4.4.0 + image: storage-backend-controller:4.5.0 imagePullPolicy: "IfNotPresent" env: - name: CSI_NAMESPACE @@ -610,7 +607,7 @@ spec: cpu: 50m memory: 128Mi - name: storage-backend-sidecar - image: storage-backend-sidecar:4.4.0 + image: storage-backend-sidecar:4.5.0 imagePullPolicy: "IfNotPresent" env: - name: DRCSI_ENDPOINT @@ -647,7 +644,7 @@ spec: cpu: 50m memory: 128Mi - name: huawei-csi-extender - image: huawei-csi-extender:4.4.0 + image: huawei-csi-extender:4.5.0 imagePullPolicy: "IfNotPresent" env: - name: DRCSI_ENDPOINT @@ -686,7 +683,7 @@ spec: cpu: 50m memory: 128Mi - name: huawei-csi-driver - image: huawei-csi:4.4.0 + image: huawei-csi:4.5.0 imagePullPolicy: "IfNotPresent" args: - "--endpoint=$(CSI_ENDPOINT)" @@ -697,7 +694,6 @@ spec: - "--logging-module=file" - "--log-level=info" - "--volume-name-prefix=pvc" - - "--enable-label=false" - "--log-file-dir=/var/log/huawei" - "--log-file-size=20M" - "--max-backups=9" diff --git a/manual/esdk/deploy/huawei-csi-controller.yaml b/manual/esdk/deploy/huawei-csi-controller.yaml index ecff60c7..cd27f4b0 100644 --- a/manual/esdk/deploy/huawei-csi-controller.yaml +++ b/manual/esdk/deploy/huawei-csi-controller.yaml @@ -384,9 +384,6 @@ rules: - apiGroups: [ "" ] resources: [ "secrets" ] verbs: [ "get" ] - - apiGroups: [ "xuanwu.huawei.io" ] - resources: [ "resourcetopologies" ] - verbs: [ "create", "get", "update", "delete" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -539,7 +536,7 @@ spec: cpu: 50m memory: 128Mi - name: storage-backend-controller - image: storage-backend-controller:4.4.0 + image: storage-backend-controller:4.5.0 imagePullPolicy: "IfNotPresent" env: - name: CSI_NAMESPACE @@ -578,7 +575,7 @@ spec: cpu: 50m memory: 128Mi - name: storage-backend-sidecar - image: storage-backend-sidecar:4.4.0 + image: storage-backend-sidecar:4.5.0 imagePullPolicy: "IfNotPresent" env: - name: DRCSI_ENDPOINT @@ -615,7 +612,7 @@ spec: cpu: 50m memory: 128Mi - name: huawei-csi-driver - image: huawei-csi:4.4.0 + image: huawei-csi:4.5.0 imagePullPolicy: "IfNotPresent" args: - "--endpoint=$(CSI_ENDPOINT)" @@ -626,7 +623,6 @@ spec: - "--logging-module=file" - "--log-level=info" - "--volume-name-prefix=pvc" - - "--enable-label=false" - "--log-file-dir=/var/log/huawei" - "--log-file-size=20M" - "--max-backups=9" diff --git a/manual/esdk/deploy/huawei-csi-node.yaml b/manual/esdk/deploy/huawei-csi-node.yaml index 57b21765..aaecb565 100644 --- a/manual/esdk/deploy/huawei-csi-node.yaml +++ b/manual/esdk/deploy/huawei-csi-node.yaml @@ -89,9 +89,6 @@ rules: - storagebackendcontents verbs: - get - - apiGroups: [ "xuanwu.huawei.io" ] - resources: [ "resourcetopologies" ] - verbs: [ "create", "get", "update", "delete" ] - apiGroups: - "" resources: @@ -169,7 +166,7 @@ spec: cpu: 50m memory: 128Mi - name: huawei-csi-driver - image: huawei-csi:4.4.0 + image: huawei-csi:4.5.0 imagePullPolicy: "IfNotPresent" args: - "--endpoint=/csi/csi.sock" diff --git a/pkg/client/clientset/versioned/typed/xuanwu/v1/fake/fake_xuanwu_client.go b/pkg/client/clientset/versioned/typed/xuanwu/v1/fake/fake_xuanwu_client.go index d5834edb..4084aafc 100644 --- a/pkg/client/clientset/versioned/typed/xuanwu/v1/fake/fake_xuanwu_client.go +++ b/pkg/client/clientset/versioned/typed/xuanwu/v1/fake/fake_xuanwu_client.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,10 +26,6 @@ type FakeXuanwuV1 struct { *testing.Fake } -func (c *FakeXuanwuV1) ResourceTopologies() v1.ResourceTopologyInterface { - return &FakeResourceTopologies{c} -} - func (c *FakeXuanwuV1) StorageBackendClaims(namespace string) v1.StorageBackendClaimInterface { return &FakeStorageBackendClaims{c, namespace} } diff --git a/pkg/client/clientset/versioned/typed/xuanwu/v1/generated_expansion.go b/pkg/client/clientset/versioned/typed/xuanwu/v1/generated_expansion.go index 7a6c4a2c..4f8b7ddd 100644 --- a/pkg/client/clientset/versioned/typed/xuanwu/v1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/xuanwu/v1/generated_expansion.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,8 +15,6 @@ package v1 -type ResourceTopologyExpansion interface{} - type StorageBackendClaimExpansion interface{} type StorageBackendContentExpansion interface{} diff --git a/pkg/client/clientset/versioned/typed/xuanwu/v1/xuanwu_client.go b/pkg/client/clientset/versioned/typed/xuanwu/v1/xuanwu_client.go index 7d7292ad..d7548580 100644 --- a/pkg/client/clientset/versioned/typed/xuanwu/v1/xuanwu_client.go +++ b/pkg/client/clientset/versioned/typed/xuanwu/v1/xuanwu_client.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import ( type XuanwuV1Interface interface { RESTClient() rest.Interface - ResourceTopologiesGetter StorageBackendClaimsGetter StorageBackendContentsGetter VolumeModifyClaimsGetter @@ -37,10 +36,6 @@ type XuanwuV1Client struct { restClient rest.Interface } -func (c *XuanwuV1Client) ResourceTopologies() ResourceTopologyInterface { - return newResourceTopologies(c) -} - func (c *XuanwuV1Client) StorageBackendClaims(namespace string) StorageBackendClaimInterface { return newStorageBackendClaims(c, namespace) } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 2d4e12ae..f7aaea89 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -50,8 +50,6 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=xuanwu.huawei.io, Version=v1 - case v1.SchemeGroupVersion.WithResource("resourcetopologies"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Xuanwu().V1().ResourceTopologies().Informer()}, nil case v1.SchemeGroupVersion.WithResource("storagebackendclaims"): return &genericInformer{resource: resource.GroupResource(), informer: f.Xuanwu().V1().StorageBackendClaims().Informer()}, nil case v1.SchemeGroupVersion.WithResource("storagebackendcontents"): diff --git a/pkg/client/informers/externalversions/xuanwu/v1/interface.go b/pkg/client/informers/externalversions/xuanwu/v1/interface.go index 5c58552a..3506e218 100644 --- a/pkg/client/informers/externalversions/xuanwu/v1/interface.go +++ b/pkg/client/informers/externalversions/xuanwu/v1/interface.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,8 +21,6 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { - // ResourceTopologies returns a ResourceTopologyInformer. - ResourceTopologies() ResourceTopologyInformer // StorageBackendClaims returns a StorageBackendClaimInformer. StorageBackendClaims() StorageBackendClaimInformer // StorageBackendContents returns a StorageBackendContentInformer. @@ -44,11 +42,6 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } -// ResourceTopologies returns a ResourceTopologyInformer. -func (v *version) ResourceTopologies() ResourceTopologyInformer { - return &resourceTopologyInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} -} - // StorageBackendClaims returns a StorageBackendClaimInformer. func (v *version) StorageBackendClaims() StorageBackendClaimInformer { return &storageBackendClaimInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/listers/xuanwu/v1/expansion_generated.go b/pkg/client/listers/xuanwu/v1/expansion_generated.go index 144ee4c8..014589c6 100644 --- a/pkg/client/listers/xuanwu/v1/expansion_generated.go +++ b/pkg/client/listers/xuanwu/v1/expansion_generated.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,10 +15,6 @@ package v1 -// ResourceTopologyListerExpansion allows custom methods to be added to -// ResourceTopologyLister. -type ResourceTopologyListerExpansion interface{} - // StorageBackendClaimListerExpansion allows custom methods to be added to // StorageBackendClaimLister. type StorageBackendClaimListerExpansion interface{} diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index cfa93edb..c8f798e2 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -22,7 +22,7 @@ type FileType string const ( // ProviderVersion defines provider version - ProviderVersion = "4.4.0" + ProviderVersion = "4.5.0" // ProviderVendorName defines provider vendor name ProviderVendorName = "Huawei" // EndpointDirPermission defines permission of endpoint dir @@ -62,8 +62,6 @@ const ( PVKind = "PersistentVolume" // PodKind is defined by k8s PodKind = "Pod" - // TopologyKind is topology resource kind - TopologyKind = "ResourceTopology" // KubernetesV1 is kubernetes v1 api version KubernetesV1 = "v1" @@ -78,6 +76,11 @@ const ( // AllocationUnitBytes default is 512 AllocationUnitBytes = 512 + + // DefaultIntBase is the default value of int base + DefaultIntBase = 10 + // DefaultIntBitSize is the default value of bit size + DefaultIntBitSize = 64 ) var ( diff --git a/pkg/constants/storage.go b/pkg/constants/storage.go index 44921011..f01d545d 100644 --- a/pkg/constants/storage.go +++ b/pkg/constants/storage.go @@ -30,10 +30,19 @@ const ( // DoradoV615 is Dorado V6.1.5 DoradoV615 = "6.1.5" - // MinVersionSupportLabel version gte 6.1.7 support label function - MinVersionSupportLabel = "6.1.7" + // MinVersionSupportNfsPlus version gte 6.1.7 support label function + MinVersionSupportNfsPlus = "6.1.7" // OceanStorNas storage type is oceanstor-nas OceanStorNas = "oceanstor-nas" + + // CloneSpeedLevel1 means level1 of the clone speed + CloneSpeedLevel1 = 1 + // CloneSpeedLevel2 means level2 of the clone speed + CloneSpeedLevel2 = 2 + // CloneSpeedLevel3 means level3 of the clone speed + CloneSpeedLevel3 = 3 + // CloneSpeedLevel4 means level4 of the clone speed + CloneSpeedLevel4 = 4 ) // BackendCapability backend capability @@ -65,6 +74,3 @@ var SupportApplicationType BackendCapability = "SupportApplicationType" // SupportMetroNAS defines backend capability SupportMetroNAS var SupportMetroNAS BackendCapability = "SupportMetroNAS" - -// SupportLabel defines backend capability SupportLabel -var SupportLabel BackendCapability = "SupportLabel" diff --git a/pkg/modify/claim_worker.go b/pkg/modify/claim_worker.go index 5cfbd267..1b0e2fd4 100644 --- a/pkg/modify/claim_worker.go +++ b/pkg/modify/claim_worker.go @@ -31,10 +31,10 @@ import ( apiErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/utils/strings/slices" xuanwuv1 "huawei-csi-driver/client/apis/xuanwu/v1" pkgutils "huawei-csi-driver/pkg/utils" + "huawei-csi-driver/storage/oceanstor/client" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) @@ -49,6 +49,9 @@ const ( // HyperMetroFeatures hyperMetro features HyperMetroFeatures = "hyperMetro" + // MetroPairSyncSpeed creates hyper metro pair synchronization speed + MetroPairSyncSpeed = "metroPairSyncSpeed" + // CreateFailedReason reason of created claim failed CreateFailedReason = "CreatingFailed" @@ -69,12 +72,15 @@ const ( ) var ( - supportFeatures = []string{HyperMetroFeatures} + supportFeatures = []string{HyperMetroFeatures, MetroPairSyncSpeed} syncFuncList []func(context.Context, *xuanwuv1.VolumeModifyClaim) (*xuanwuv1.VolumeModifyClaim, error) onceInitSyncFuncList sync.Once deleteFuncList []func(context.Context, *xuanwuv1.VolumeModifyClaim) (*xuanwuv1.VolumeModifyClaim, error) onceInitDeleteFuncList sync.Once - featureValueCheckFunc = map[string]func(string) error{HyperMetroFeatures: checkHyperMetro} + featureCheckFunc = map[string]func(string, map[string]string) error{ + HyperMetroFeatures: checkHyperMetro, + MetroPairSyncSpeed: checkMetroPairSyncSpeed, + } ) func (ctrl *VolumeModifyController) syncClaimWork(ctx context.Context, name string) error { @@ -157,7 +163,7 @@ func (ctrl *VolumeModifyController) setClaimFinalizers(ctx context.Context, return claim, nil } - if slices.Contains(claim.Finalizers, ProtectClaimFinalizer) { + if utils.Contains(claim.Finalizers, ProtectClaimFinalizer) { return claim, nil } @@ -446,7 +452,7 @@ func (ctrl *VolumeModifyController) findNoSupportFeatures(params map[string]stri var notSupports []string supports := make(map[string]string) for key, value := range params { - if !slices.Contains(supportFeatures, key) { + if !utils.Contains(supportFeatures, key) { notSupports = append(notSupports, key) continue } @@ -645,12 +651,12 @@ func checkParameters(params map[string]string) error { var notSupports []string supports := make(map[string]string) for key, value := range params { - if !slices.Contains(supportFeatures, key) { + if !utils.Contains(supportFeatures, key) { notSupports = append(notSupports, key) continue } - if check, ok := featureValueCheckFunc[key]; ok { - if err := check(value); err != nil { + if check, ok := featureCheckFunc[key]; ok { + if err := check(value, params); err != nil { return err } } @@ -665,13 +671,33 @@ func checkParameters(params map[string]string) error { strings.Join(notSupports, ","), strings.Join(supportFeatures, ",")) } -func checkHyperMetro(value string) error { +func checkHyperMetro(value string, _ map[string]string) error { if value == "true" { return nil } return fmt.Errorf("check spec failed: paramter hyperMetro can only be set to 'true'") } +func checkMetroPairSyncSpeed(value string, params map[string]string) error { + if hyperMetro, exist := params[HyperMetroFeatures]; !exist || hyperMetro != "true" { + return fmt.Errorf("check spec failed: " + + "parameter metroPairSyncSpeed can be configured only when hyperMetro is set to true") + } + + speed, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("check spec failed: paramter metroPairSyncSpeed can only be an integer") + } + + if speed < client.MetroPairSyncSpeedLow || speed > client.MetroPairSyncSpeedHighest { + return fmt.Errorf( + "check spec failed: paramter metroPairSyncSpeed must be between %d and %d, but got [%d]", + client.MetroPairSyncSpeedLow, client.MetroPairSyncSpeedHighest, speed) + } + + return nil +} + func (ctrl *VolumeModifyController) updateClaimStatusWithRetry(ctx context.Context, claim *xuanwuv1.VolumeModifyClaim, retryTimes int) (*xuanwuv1.VolumeModifyClaim, error) { var err error diff --git a/pkg/modify/claim_worker_test.go b/pkg/modify/claim_worker_test.go index 5c8e67a7..efc11a62 100644 --- a/pkg/modify/claim_worker_test.go +++ b/pkg/modify/claim_worker_test.go @@ -27,12 +27,12 @@ import ( apiErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" - "k8s.io/utils/strings/slices" xuanwuv1 "huawei-csi-driver/client/apis/xuanwu/v1" clientSet "huawei-csi-driver/pkg/client/clientset/versioned" "huawei-csi-driver/pkg/client/clientset/versioned/fake" backendInformers "huawei-csi-driver/pkg/client/informers/externalversions" + "huawei-csi-driver/utils" ) func TestModifyClaimController_syncClaimWork_WhenGetClaimFromListerFailed(t *testing.T) { @@ -92,7 +92,7 @@ func TestModifyClaimController_setClaimFinalizers_WhenStatusIsNil(t *testing.T) "want nil, but got %v", err) } - if slices.Contains(claim.Finalizers, ProtectClaimFinalizer) { + if utils.Contains(claim.Finalizers, ProtectClaimFinalizer) { t.Errorf("TestModifyClaimController_setClaimFinalizers_WhenStatusIsNil failed, "+ "want not finalzer, but got %v", claim.Finalizers) } @@ -126,7 +126,7 @@ func TestModifyClaimController_setClaimFinalizers_WhenPhaseIsPending(t *testing. "want nil, but got %v", err) } - if !slices.Contains(claim.Finalizers, ProtectClaimFinalizer) { + if !utils.Contains(claim.Finalizers, ProtectClaimFinalizer) { t.Errorf("TestModifyClaimController_setClaimFinalizers_WhenPhaseIsPending failed, "+ "want proctect finalzer, but got %v", claim.Finalizers) } diff --git a/pkg/modify/content_worker.go b/pkg/modify/content_worker.go index f7d9356d..e40767b5 100644 --- a/pkg/modify/content_worker.go +++ b/pkg/modify/content_worker.go @@ -28,7 +28,6 @@ import ( corev1 "k8s.io/api/core/v1" apiErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/strings/slices" xuanwuv1 "huawei-csi-driver/client/apis/xuanwu/v1" "huawei-csi-driver/lib/drcsi" @@ -145,7 +144,7 @@ func (ctrl *VolumeModifyController) setContentFinalizers(ctx context.Context, defer log.AddContext(ctx).Infof("content %s added finalizer ", content.Name) contentClone := content.DeepCopy() - if slices.Contains(content.Finalizers, ProtectContentFinalizer) { + if utils.Contains(content.Finalizers, ProtectContentFinalizer) { return contentClone, nil } contentClone.ObjectMeta.Finalizers = append(contentClone.ObjectMeta.Finalizers, ProtectContentFinalizer) diff --git a/pkg/sidecar/controller/sidecar_controller.go b/pkg/sidecar/controller/sidecar_controller.go index 42262e5b..33b2a180 100644 --- a/pkg/sidecar/controller/sidecar_controller.go +++ b/pkg/sidecar/controller/sidecar_controller.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ limitations under the License. */ -// Package controller used deal with the backend backend content resources +// Package controller used deal with the backend content resources package controller import ( @@ -31,30 +31,35 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" - "huawei-csi-driver/pkg/utils" - xuanwuv1 "huawei-csi-driver/client/apis/xuanwu/v1" clientSet "huawei-csi-driver/pkg/client/clientset/versioned" backendInformers "huawei-csi-driver/pkg/client/informers/externalversions/xuanwu/v1" backendListers "huawei-csi-driver/pkg/client/listers/xuanwu/v1" storageBackend "huawei-csi-driver/pkg/storage-backend/handle" + "huawei-csi-driver/pkg/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" +) + +const ( + defaultRetryIntervalStart = 5 * time.Second + defaultRetryIntervalMax = 5 * time.Minute + defaultProvisionTimeout = 5 * time.Minute ) var ( retryIntervalStart = flag.Duration( "retry-interval-start", - 5*time.Second, + defaultRetryIntervalStart, "Initial retry interval of failed storageBackend creation or deletion. "+ "It doubles with each failure, up to retry-interval-max.") retryIntervalMax = flag.Duration( "retry-interval-max", - 5*time.Minute, + defaultRetryIntervalMax, "Maximum retry interval of failed storageBackend creation or deletion.") provisionTimeout = flag.Duration( "provision-timeout", - 5*time.Minute, + defaultProvisionTimeout, "The timeout of the provision storage backend.") ) @@ -258,10 +263,13 @@ func (ctrl *backendController) syncContentByKey(ctx context.Context, objKey stri content, err := ctrl.contentLister.Get(name) if err == nil { - if ctrl.isMatchProvider(content) { - // the content exists in informer cache, the handle event must be one of "create/update/sync" - return ctrl.updateContent(ctx, content) + if !ctrl.isMatchProvider(content) { + return fmt.Errorf("backend provider [%s] does not match driver provider [%s]", + content.Spec.Provider, ctrl.providerName) } + + // the content exists in informer cache, the handle event must be one of "create/update/sync" + return ctrl.updateContent(ctx, content) } if !apiErrors.IsNotFound(err) { @@ -325,7 +333,7 @@ func (ctrl *backendController) syncContent(ctx context.Context, content *xuanwuv log.AddContext(ctx).Debugf("Start to sync content %s.", content.Name) defer log.AddContext(ctx).Debugf("Finished sync content %s.", content.Name) - syncTask := taskflow.NewTaskFlow(ctx, "Sync-StorageBackendContent") + syncTask := flow.NewTaskFlow(ctx, "Sync-StorageBackendContent") syncTask.AddTask("Init-Content-Status", ctrl.initContentStatusTask, nil) syncTask.AddTask("Delete-Content", ctrl.deleteContentTask, nil) syncTask.AddTask("Create-Content", ctrl.createContentTask, nil) diff --git a/pkg/sidecar/controller/sidecar_handler.go b/pkg/sidecar/controller/sidecar_handler.go index cb40c099..8b4954ed 100644 --- a/pkg/sidecar/controller/sidecar_handler.go +++ b/pkg/sidecar/controller/sidecar_handler.go @@ -65,8 +65,7 @@ func (cdr *drCSIHandler) DeleteStorageBackend(ctx context.Context, backendName s // UpdateStorageBackend update the storageBackend func (cdr *drCSIHandler) UpdateStorageBackend(ctx context.Context, content *xuanwuv1.StorageBackendContent) error { - return cdr.backend.UpdateStorageBackend(ctx, content.Name, content.Spec.BackendClaim, - content.Spec.ConfigmapMeta, content.Spec.SecretMeta, content.Spec.Parameters) + return cdr.backend.UpdateStorageBackend(ctx, content) } // GetStorageBackendStats get all backend info from the provider diff --git a/pkg/storage-backend/controller/claim_sync.go b/pkg/storage-backend/controller/claim_sync.go index 564506db..fe5e267a 100644 --- a/pkg/storage-backend/controller/claim_sync.go +++ b/pkg/storage-backend/controller/claim_sync.go @@ -29,8 +29,8 @@ import ( "huawei-csi-driver/csi/backend" "huawei-csi-driver/pkg/finalizers" "huawei-csi-driver/pkg/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" ) // syncClaimByKey processes a StorageBackendClaim request. @@ -94,7 +94,7 @@ func (ctrl *BackendController) syncClaim(ctx context.Context, storageBackend *xu log.AddContext(ctx).Infof("Start to syncClaim %s.", utils.StorageBackendClaimKey(storageBackend)) defer log.AddContext(ctx).Infof("Finished syncClaim %s.", utils.StorageBackendClaimKey(storageBackend)) - syncTask := taskflow.NewTaskFlow(ctx, "Sync-StorageBackendClaim") + syncTask := flow.NewTaskFlow(ctx, "Sync-StorageBackendClaim") syncTask.AddTask("Set-Claim-Status-Pending", ctrl.setClaimStatusTask, nil) syncTask.AddTask("Remove-Configmap-Finalizer", ctrl.removeConfigmapFinalizerTask, nil) syncTask.AddTask("Remove-Secret-Finalizer", ctrl.removeSecretFinalizerTask, nil) diff --git a/pkg/storage-backend/controller/controller.go b/pkg/storage-backend/controller/controller.go index bf704655..a0940678 100644 --- a/pkg/storage-backend/controller/controller.go +++ b/pkg/storage-backend/controller/controller.go @@ -1,5 +1,5 @@ /* - Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -39,19 +39,25 @@ import ( "huawei-csi-driver/utils/log" ) +const ( + defaultRetryIntervalStart = 5 * time.Second + defaultRetryIntervalMax = 5 * time.Minute + defaultProvisionTimeout = 5 * time.Minute +) + var ( retryIntervalStart = flag.Duration( "retry-interval-start", - 5*time.Second, + defaultRetryIntervalStart, "Initial retry interval of failed storageBackend creation or deletion. "+ "It doubles with each failure, up to retry-interval-max.") retryIntervalMax = flag.Duration( "retry-interval-max", - 5*time.Minute, + defaultRetryIntervalMax, "Maximum retry interval of failed storageBackend creation or deletion.") provisionTimeout = flag.Duration( "provision-timeout", - 5*time.Minute, + defaultProvisionTimeout, "The timeout of the provision storage backend.") ) diff --git a/pkg/storage-backend/handle/storage_backend.go b/pkg/storage-backend/handle/storage_backend.go index 19248b9a..088548f5 100644 --- a/pkg/storage-backend/handle/storage_backend.go +++ b/pkg/storage-backend/handle/storage_backend.go @@ -21,6 +21,7 @@ import ( "google.golang.org/grpc" + xuanwuv1 "huawei-csi-driver/client/apis/xuanwu/v1" "huawei-csi-driver/lib/drcsi" "huawei-csi-driver/lib/drcsi/rpc" "huawei-csi-driver/utils/log" @@ -34,8 +35,7 @@ type BackendInterfaces interface { // RemoveStorageBackend remove the storageBackend from provider RemoveStorageBackend(ctx context.Context, backendName string) (err error) // UpdateStorageBackend update the storageBackend - UpdateStorageBackend(ctx context.Context, contentName, backendName, configmapMeta, secretMeta string, - parameters map[string]string) error + UpdateStorageBackend(ctx context.Context, content *xuanwuv1.StorageBackendContent) error // GetStorageBackendStats get all backend info from the provider GetStorageBackendStats(ctx context.Context, contentName, backendName string) (*drcsi.GetBackendStatsResponse, error) } @@ -94,15 +94,14 @@ func updateStorageBackend(ctx context.Context, conn *grpc.ClientConn, req *drcsi } // UpdateStorageBackend update the storageBackend -func (b *backend) UpdateStorageBackend(ctx context.Context, contentName, backendName, configmapMeta, secretMeta string, - parameters map[string]string) error { - log.AddContext(ctx).Infof("UpdateStorageBackend of backend %s", backendName) +func (b *backend) UpdateStorageBackend(ctx context.Context, content *xuanwuv1.StorageBackendContent) error { + log.AddContext(ctx).Infof("UpdateStorageBackend of backend %s", content.Name) req := drcsi.UpdateStorageBackendRequest{ - Name: contentName, - BackendId: backendName, - ConfigmapMeta: configmapMeta, - SecretMeta: secretMeta, - Parameters: parameters, + Name: content.Name, + BackendId: content.Spec.BackendClaim, + ConfigmapMeta: content.Spec.ConfigmapMeta, + SecretMeta: content.Spec.SecretMeta, + Parameters: content.Spec.Parameters, } _, err := updateStorageBackend(ctx, b.conn, &req) diff --git a/pkg/webhook/utils.go b/pkg/webhook/utils.go index c0b8a033..7547228b 100644 --- a/pkg/webhook/utils.go +++ b/pkg/webhook/utils.go @@ -1,5 +1,5 @@ /* -Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. +Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ import ( "huawei-csi-driver/utils/log" ) +const certUntilYears = 99 + // GenerateCertificate Self Signed certificate using given CN, returns x509 cert // and priv key in PEM format func GenerateCertificate(ctx context.Context, cn string, dnsName string) ([]byte, []byte, error) { @@ -55,7 +57,7 @@ func GenerateCertificate(ctx context.Context, cn string, dnsName string) ([]byte CommonName: cn, }, NotBefore: time.Now(), - NotAfter: time.Now().AddDate(99, 0, 0), + NotAfter: time.Now().AddDate(certUntilYears, 0, 0), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, @@ -94,7 +96,7 @@ func GetTLSCertificate(cert, priv []byte) (tls.Certificate, error) { } // CreateCertSecrets creates k8s secret to store signed cert data -func CreateCertSecrets(ctx context.Context, webHookCfg WebHook, cert, key []byte, ns string) (*v1.Secret, error) { +func CreateCertSecrets(ctx context.Context, webHookCfg Config, cert, key []byte, ns string) (*v1.Secret, error) { secretData := make(map[string][]byte) secretData[webHookCfg.PrivateKey] = key secretData[webHookCfg.PrivateCert] = cert diff --git a/pkg/webhook/validate_webhook.go b/pkg/webhook/validate_webhook.go index a36d6c56..8e3683b5 100644 --- a/pkg/webhook/validate_webhook.go +++ b/pkg/webhook/validate_webhook.go @@ -17,6 +17,7 @@ package webhook import ( "context" + "reflect" admissionV1 "k8s.io/api/admissionregistration/v1" apisErrors "k8s.io/apimachinery/pkg/api/errors" @@ -44,28 +45,66 @@ type AdmissionRule struct { } // CreateValidateWebhook create new webhook config if not exist already -func CreateValidateWebhook(ctx context.Context, admissionWebhook AdmissionWebHookCFG, - caBundle []byte, ns string) error { +func CreateValidateWebhook(ctx context.Context, webHookCfg AdmissionWebHookCFG, caBundle []byte, ns string) error { + webhook := newValidateWebhook(webHookCfg, caBundle, ns) + req := &admissionV1.ValidatingWebhookConfiguration{ + ObjectMeta: metaV1.ObjectMeta{Name: webHookCfg.WebhookName}, + Webhooks: []admissionV1.ValidatingWebhook{webhook}, + } + + foundWebhookCfg, err := admission.Instance().GetValidatingWebhookCfg(req.Name) + if err != nil { + if !apisErrors.IsNotFound(err) { + log.AddContext(ctx).Errorf("get webhook configuration [%s] failed: %v", req.Name, err) + return err + } + + // no webhook configuration in k8s cluster, we need to create a new one. + if _, err := admission.Instance().CreateValidatingWebhookCfg(req); err != nil { + log.AddContext(ctx).Errorf("create webhook configuration [%s] failed: %v", req.Name, err) + return err + } + log.AddContext(ctx).Infof("webhook configuration [%s] has been created", req.Name) + return nil + } + + if reflect.DeepEqual(foundWebhookCfg.Webhooks, req.Webhooks) { + return nil + } + + // webhook configuration has changed, we need to update it. + foundWebhookCfg.Webhooks = req.Webhooks + if _, err := admission.Instance().UpdateValidatingWebhookCfg(foundWebhookCfg); err != nil { + log.AddContext(ctx).Errorf("update webhook configuration failed: %v", err) + return err + } + + log.AddContext(ctx).Infof("webhook [%s] has been updated", req.Name) + + return nil +} + +func newValidateWebhook(webhookCfg AdmissionWebHookCFG, caBundle []byte, ns string) admissionV1.ValidatingWebhook { sideEffect := admissionV1.SideEffectClassNoneOnDryRun failurePolicy := admissionV1.Fail matchPolicy := admissionV1.Exact - webhook := admissionV1.ValidatingWebhook{ - Name: admissionWebhook.WebhookName, + return admissionV1.ValidatingWebhook{ + Name: webhookCfg.WebhookName, ClientConfig: admissionV1.WebhookClientConfig{ Service: &admissionV1.ServiceReference{ - Name: admissionWebhook.ServiceName, + Name: webhookCfg.ServiceName, Namespace: ns, - Path: &admissionWebhook.WebhookPath, - Port: &admissionWebhook.WebhookPort, + Path: &webhookCfg.WebhookPath, + Port: &webhookCfg.WebhookPort, }, CABundle: caBundle, }, Rules: []admissionV1.RuleWithOperations{{ - Operations: admissionWebhook.AdmissionOps, + Operations: webhookCfg.AdmissionOps, Rule: admissionV1.Rule{ - APIGroups: admissionWebhook.AdmissionRule.APIGroups, - APIVersions: admissionWebhook.AdmissionRule.APIVersions, - Resources: admissionWebhook.AdmissionRule.Resources, + APIGroups: webhookCfg.AdmissionRule.APIGroups, + APIVersions: webhookCfg.AdmissionRule.APIVersions, + Resources: webhookCfg.AdmissionRule.Resources, }, }}, SideEffects: &sideEffect, @@ -73,19 +112,4 @@ func CreateValidateWebhook(ctx context.Context, admissionWebhook AdmissionWebHoo AdmissionReviewVersions: []string{"v1", "v1beta1"}, MatchPolicy: &matchPolicy, } - - req := &admissionV1.ValidatingWebhookConfiguration{ - ObjectMeta: metaV1.ObjectMeta{ - Name: admissionWebhook.WebhookName, - }, - Webhooks: []admissionV1.ValidatingWebhook{webhook}, - } - - _, err := admission.Instance().CreateValidatingWebhookCfg(req) - if err != nil && !apisErrors.IsAlreadyExists(err) { - log.AddContext(ctx).Errorf("unable to create webhook configuration: %v", err) - return err - } - log.AddContext(ctx).Infof("%v webhook v1 configured", admissionWebhook.WebhookName) - return nil } diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go index a9d449e8..87c5c1d8 100644 --- a/pkg/webhook/webhook.go +++ b/pkg/webhook/webhook.go @@ -61,8 +61,8 @@ const ( ClaimBoundFinalizer string = "storagebackend.xuanwu.huawei.io/storagebackendclaim-bound-protection" ) -// WebHook uses to start the webhook server -type WebHook struct { +// Config uses to start the webhook server +type Config struct { NamespaceEnv string DefaultNamespace string ServiceName string @@ -221,7 +221,7 @@ func (c *Controller) serve(w http.ResponseWriter, r *http.Request, admit admitHa log.AddContext(ctx).Infoln("return response success") } -func (c *Controller) getTlsCert(ctx context.Context, webHookCfg WebHook, ns string) (tls.Certificate, []byte, error) { +func (c *Controller) getTlsCert(ctx context.Context, webHookCfg Config, ns string) (tls.Certificate, []byte, error) { var tlsCert tls.Certificate var caBytes []byte certSecrets, err := app.GetGlobalConfig().K8sUtils.GetSecret(ctx, webHookCfg.SecretName, ns) @@ -271,7 +271,7 @@ func (c *Controller) getTlsCert(ctx context.Context, webHookCfg WebHook, ns stri } // Start uses to start the webhook server -func (c *Controller) Start(ctx context.Context, webHookCfg WebHook, admissionWebhooks []AdmissionWebHookCFG) error { +func (c *Controller) Start(ctx context.Context, webHookCfg Config, admissionWebhooks []AdmissionWebHookCFG) error { c.lock.Lock() defer c.lock.Unlock() @@ -316,7 +316,7 @@ func (c *Controller) Start(ctx context.Context, webHookCfg WebHook, admissionWeb } // Stop uses to stop the webhook server -func (c *Controller) Stop(ctx context.Context, webHookCfg WebHook, +func (c *Controller) Stop(ctx context.Context, webHookCfg Config, admissionWebhooks []AdmissionWebHookCFG) error { c.lock.Lock() defer c.lock.Unlock() @@ -333,7 +333,7 @@ func (c *Controller) Stop(ctx context.Context, webHookCfg WebHook, return nil } -func getNameSpaceFromEnv(webHookCfg WebHook) string { +func getNameSpaceFromEnv(webHookCfg Config) string { ns := os.Getenv(webHookCfg.NamespaceEnv) if ns == "" { ns = webHookCfg.DefaultNamespace @@ -446,7 +446,7 @@ func validateCommon(ctx context.Context, claim *xuanwuv1.StorageBackendClaim) er log.AddContext(ctx).Infof("claim name: %s", claim.Name) storageInfo, err := backend.GetStorageBackendInfo(ctx, utils.MakeMetaWithNamespace(app.GetGlobalConfig().Namespace, claim.Name), - claim.Spec.ConfigMapMeta, claim.Spec.SecretMeta, claim.Spec.CertSecret, claim.Spec.UseCert) + backend.NewGetBackendInfoArgsFromClaim(claim)) if err != nil { return err } diff --git a/pkg/webhook/webhook_cfg.go b/pkg/webhook/webhook_cfg.go index f9ecd527..7119a731 100644 --- a/pkg/webhook/webhook_cfg.go +++ b/pkg/webhook/webhook_cfg.go @@ -37,13 +37,13 @@ const ( ) // GetStorageWebHookCfg used to get storage webhook configuration -func GetStorageWebHookCfg() (WebHook, []AdmissionWebHookCFG) { +func GetStorageWebHookCfg() (Config, []AdmissionWebHookCFG) { var handleFuncPair []HandleFuncPair handleFuncPair = append(handleFuncPair, HandleFuncPair{WebhookPath: claimWebhookPath, WebHookFunc: admitStorageBackendClaim}) - webHookCfg := WebHook{ + webHookCfg := Config{ NamespaceEnv: constants.NamespaceEnv, DefaultNamespace: app.GetGlobalConfig().Namespace, ServiceName: serviceName, diff --git a/pkg/webhook/webhook_test.go b/pkg/webhook/webhook_test.go index fed0d501..a9bdc77a 100644 --- a/pkg/webhook/webhook_test.go +++ b/pkg/webhook/webhook_test.go @@ -50,7 +50,7 @@ func TestMain(m *testing.M) { // TestControllerStart test start function in normal scenario func TestControllerStart(t *testing.T) { - m := gomonkey.ApplyFunc(getNameSpaceFromEnv, func(webHookCfg WebHook) string { + m := gomonkey.ApplyFunc(getNameSpaceFromEnv, func(webHookCfg Config) string { return "namespace" }) defer m.Reset() @@ -79,7 +79,7 @@ func TestControllerStart(t *testing.T) { }) c := Controller{started: false} - webHookCfg := WebHook{ + webHookCfg := Config{ WebHookType: AdmissionWebHookValidating, PrivateKey: "privateKey", PrivateCert: "privateCert", diff --git a/storage/fusionstorage/attacher/attacher.go b/storage/fusionstorage/attacher/attacher.go index cef00ff7..a3575f4c 100644 --- a/storage/fusionstorage/attacher/attacher.go +++ b/storage/fusionstorage/attacher/attacher.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,9 +33,9 @@ import ( "huawei-csi-driver/utils/log" ) -// Attacher defines attacher client -type Attacher struct { - cli *client.Client +// VolumeAttacher defines attacher client +type VolumeAttacher struct { + cli *client.RestClient protocol string invoker string portals []string @@ -43,25 +43,36 @@ type Attacher struct { alua map[string]interface{} } +// VolumeAttacherConfig defines configurations of VolumeAttacher +type VolumeAttacherConfig struct { + Cli *client.RestClient + Protocol string + Invoker string + Portals []string + Hosts map[string]string + Alua map[string]interface{} +} + const ( // DisableAlua defines switchover mode disable alua DisableAlua = "Disable_alua" + + iscsiPortalFieldsLength = 2 ) // NewAttacher used to init a new attacher -func NewAttacher(cli *client.Client, protocol, invoker string, portals []string, - hosts map[string]string, alua map[string]interface{}) *Attacher { - return &Attacher{ - cli: cli, - protocol: protocol, - invoker: invoker, - portals: portals, - hosts: hosts, - alua: alua, +func NewAttacher(config VolumeAttacherConfig) *VolumeAttacher { + return &VolumeAttacher{ + cli: config.Cli, + protocol: config.Protocol, + invoker: config.Invoker, + portals: config.Portals, + hosts: config.Hosts, + alua: config.Alua, } } -func (p *Attacher) getHostName(ctx context.Context, parameters map[string]interface{}) (string, error) { +func (p *VolumeAttacher) getHostName(ctx context.Context, parameters map[string]interface{}) (string, error) { hostName, ok := parameters["HostName"].(string) if !ok { return "", fmt.Errorf("can not find host name,parameters:%v", parameters) @@ -70,7 +81,7 @@ func (p *Attacher) getHostName(ctx context.Context, parameters map[string]interf return hostName, nil } -func (p *Attacher) parseISCSIPortal(ctx context.Context, iscsiPortal map[string]interface{}) string { +func (p *VolumeAttacher) parseISCSIPortal(ctx context.Context, iscsiPortal map[string]interface{}) string { if iscsiPortal["iscsiStatus"] != "active" { log.AddContext(ctx).Errorf("ISCSI portal %v is not active", iscsiPortal) return "" @@ -83,7 +94,7 @@ func (p *Attacher) parseISCSIPortal(ctx context.Context, iscsiPortal map[string] } portalSplit := strings.Split(portal, ":") - if len(portalSplit) < 2 { + if len(portalSplit) < iscsiPortalFieldsLength { log.AddContext(ctx).Errorf("ISCSI portal %s is invalid", portal) return "" } @@ -98,7 +109,7 @@ func (p *Attacher) parseISCSIPortal(ctx context.Context, iscsiPortal map[string] return ip.String() } -func (p *Attacher) needUpdateIscsiHost(host map[string]interface{}, hostAlua map[string]interface{}) bool { +func (p *VolumeAttacher) needUpdateIscsiHost(host map[string]interface{}, hostAlua map[string]interface{}) bool { switchoverMode, ok := hostAlua["switchoverMode"] if !ok { return false @@ -118,7 +129,7 @@ func (p *Attacher) needUpdateIscsiHost(host map[string]interface{}, hostAlua map return false } -func (p *Attacher) createIscsiHost(ctx context.Context, hostName string) error { +func (p *VolumeAttacher) createIscsiHost(ctx context.Context, hostName string) error { host, err := p.cli.GetHostByName(ctx, hostName) if err != nil { return err @@ -135,7 +146,7 @@ func (p *Attacher) createIscsiHost(ctx context.Context, hostName string) error { return err } -func (p *Attacher) getTargetPortals(ctx context.Context) ([]string, []string, error) { +func (p *VolumeAttacher) getTargetPortals(ctx context.Context) ([]string, []string, error) { nodeResultList, err := p.cli.QueryIscsiPortal(ctx) if err != nil { log.AddContext(ctx).Errorf("Get ISCSI portals error: %v", err) @@ -184,7 +195,7 @@ func (p *Attacher) getTargetPortals(ctx context.Context) ([]string, []string, er return tgtPortals, tgtIQNs, nil } -func (p *Attacher) parseiSCSIPortalList(ctx context.Context, +func (p *VolumeAttacher) parseiSCSIPortalList(ctx context.Context, iscsiPortalList []interface{}, validIPs map[string]bool, validIQNs map[string]string) error { for _, portal := range iscsiPortalList { iscsiPortal, exist := portal.(map[string]interface{}) @@ -203,7 +214,7 @@ func (p *Attacher) parseiSCSIPortalList(ctx context.Context, return nil } -func (p *Attacher) attachIscsiInitiatorToHost(ctx context.Context, hostName string) error { +func (p *VolumeAttacher) attachIscsiInitiatorToHost(ctx context.Context, hostName string) error { parameters := map[string]interface{}{ "HostName": hostName, } @@ -236,7 +247,7 @@ func (p *Attacher) attachIscsiInitiatorToHost(ctx context.Context, hostName stri if len(host) == 0 { addInitiator = true } else if host != hostName { - return fmt.Errorf("ISCSI initiator %s is already associated to another host %s", initiatorName, host) + return fmt.Errorf("Connector initiator %s is already associated to another host %s", initiatorName, host) } } @@ -250,7 +261,7 @@ func (p *Attacher) attachIscsiInitiatorToHost(ctx context.Context, hostName stri return nil } -func (p *Attacher) isVolumeAddToHost(ctx context.Context, lunName, hostName string) (bool, error) { +func (p *VolumeAttacher) isVolumeAddToHost(ctx context.Context, lunName, hostName string) (bool, error) { hosts, err := p.cli.QueryHostOfVolume(ctx, lunName) if err != nil { return false, err @@ -265,7 +276,7 @@ func (p *Attacher) isVolumeAddToHost(ctx context.Context, lunName, hostName stri return false, nil } -func (p *Attacher) doMapping(ctx context.Context, lunName, hostName string) (string, error) { +func (p *VolumeAttacher) doMapping(ctx context.Context, lunName, hostName string) (string, error) { lun, err := p.cli.GetVolumeByName(ctx, lunName) if err != nil { log.AddContext(ctx).Errorf("Get lun %s error: %v", lunName, err) @@ -304,7 +315,7 @@ func (p *Attacher) doMapping(ctx context.Context, lunName, hostName string) (str return lun["wwn"].(string), nil } -func (p *Attacher) doUnmapping(ctx context.Context, lunName, hostName string) (string, error) { +func (p *VolumeAttacher) doUnmapping(ctx context.Context, lunName, hostName string) (string, error) { lun, err := p.getLunInfo(ctx, lunName) if lun == nil { return "", err @@ -337,7 +348,7 @@ func (p *Attacher) doUnmapping(ctx context.Context, lunName, hostName string) (s return lun["wwn"].(string), nil } -func (p *Attacher) getMappingProperties(ctx context.Context, +func (p *VolumeAttacher) getMappingProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]interface{}) (map[string]interface{}, error) { tgtPortals, tgtIQNs, err := p.getTargetPortals(ctx) if err != nil { @@ -359,7 +370,7 @@ func (p *Attacher) getMappingProperties(ctx context.Context, return connectInfo, nil } -func (p *Attacher) iSCSIControllerAttach(ctx context.Context, lunInfo utils.Volume, +func (p *VolumeAttacher) iSCSIControllerAttach(ctx context.Context, lunInfo utils.Volume, parameters map[string]interface{}) ( map[string]interface{}, error) { hostName, err := p.getHostName(ctx, parameters) @@ -404,7 +415,7 @@ func (p *Attacher) iSCSIControllerAttach(ctx context.Context, lunInfo utils.Volu } // SCSIControllerAttach used to attach volume to host -func (p *Attacher) SCSIControllerAttach(ctx context.Context, +func (p *VolumeAttacher) SCSIControllerAttach(ctx context.Context, lunInfo utils.Volume, parameters map[string]interface{}) (string, error) { hostName, err := p.getHostName(ctx, parameters) @@ -432,7 +443,7 @@ func (p *Attacher) SCSIControllerAttach(ctx context.Context, } // ControllerDetach used to detach volume from host -func (p *Attacher) ControllerDetach(ctx context.Context, +func (p *VolumeAttacher) ControllerDetach(ctx context.Context, lunName string, parameters map[string]interface{}) (string, error) { hostName, err := p.getHostName(ctx, parameters) @@ -455,7 +466,7 @@ func (p *Attacher) ControllerDetach(ctx context.Context, } // ControllerAttach used to attach volume and return mapping info -func (p *Attacher) ControllerAttach(ctx context.Context, +func (p *VolumeAttacher) ControllerAttach(ctx context.Context, lunName string, parameters map[string]interface{}) (map[string]interface{}, error) { @@ -484,10 +495,10 @@ func (p *Attacher) ControllerAttach(ctx context.Context, } // NodeStage used to stage node -func (p *Attacher) NodeStage(ctx context.Context, +func (p *VolumeAttacher) NodeStage(ctx context.Context, lunInfo utils.Volume, parameters map[string]interface{}) (*connector.ConnectInfo, error) { - var conn connector.Connector + var conn connector.VolumeConnector var mappingInfo map[string]interface{} var err error if p.protocol == "iscsi" { @@ -514,7 +525,7 @@ func (p *Attacher) NodeStage(ctx context.Context, } // NodeUnstage used to unstage node -func (p *Attacher) NodeUnstage(ctx context.Context, +func (p *VolumeAttacher) NodeUnstage(ctx context.Context, lunName string, parameters map[string]interface{}) (*connector.DisConnectInfo, error) { lun, err := p.getLunInfo(ctx, lunName) @@ -522,7 +533,7 @@ func (p *Attacher) NodeUnstage(ctx context.Context, return nil, err } - var conn connector.Connector + var conn connector.VolumeConnector if p.protocol == "iscsi" { conn = connector.GetConnector(ctx, connector.ISCSIDriver) } else { @@ -540,7 +551,7 @@ func (p *Attacher) NodeUnstage(ctx context.Context, }, nil } -func (p *Attacher) getLunInfo(ctx context.Context, lunName string) (map[string]interface{}, error) { +func (p *VolumeAttacher) getLunInfo(ctx context.Context, lunName string) (map[string]interface{}, error) { lun, err := p.cli.GetVolumeByName(ctx, lunName) if err != nil { log.AddContext(ctx).Errorf("Get lun %s error: %v", lunName, err) diff --git a/storage/fusionstorage/client/client.go b/storage/fusionstorage/client/client.go index 0830c19c..c5f5aff8 100644 --- a/storage/fusionstorage/client/client.go +++ b/storage/fusionstorage/client/client.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -53,7 +53,8 @@ const ( // IPLock defines error code of ip lock IPLock = 1077949071 - unconnectedError = "unconnected" + unconnectedError = "unconnected" + defaultHttpTimeout = 60 * time.Second ) var ( @@ -81,8 +82,8 @@ func isFilterLog(method, url string) bool { return exist && filter[url] } -// Client defines fusion storage client -type Client struct { +// RestClient defines fusion storage client +type RestClient struct { url string user string secretNamespace string @@ -114,7 +115,7 @@ type NewClientConfig struct { } // NewClient used to init a new fusion storage client -func NewClient(ctx context.Context, clientConfig *NewClientConfig) *Client { +func NewClient(ctx context.Context, clientConfig *NewClientConfig) *RestClient { var err error var parallelCount int @@ -131,7 +132,7 @@ func NewClient(ctx context.Context, clientConfig *NewClientConfig) *Client { log.AddContext(ctx).Infof("Init parallel count is %d", parallelCount) clientSemaphore = utils.NewSemaphore(parallelCount) - return &Client{ + return &RestClient{ url: clientConfig.Url, user: clientConfig.User, secretName: clientConfig.SecretName, @@ -144,7 +145,7 @@ func NewClient(ctx context.Context, clientConfig *NewClientConfig) *Client { } // DuplicateClient used to duplicate client -func (cli *Client) DuplicateClient() *Client { +func (cli *RestClient) DuplicateClient() *RestClient { dup := *cli dup.client = nil @@ -152,7 +153,7 @@ func (cli *Client) DuplicateClient() *Client { } // ValidateLogin try to login fusion storage by secret -func (cli *Client) ValidateLogin(ctx context.Context) error { +func (cli *RestClient) ValidateLogin(ctx context.Context) error { jar, err := cookiejar.New(nil) if err != nil { log.AddContext(ctx).Errorf("create jar failed, error: %v", err) @@ -169,7 +170,7 @@ func (cli *Client) ValidateLogin(ctx context.Context) error { TLSClientConfig: &tls.Config{InsecureSkipVerify: !useCert, RootCAs: certPool}, }, Jar: jar, - Timeout: 60 * time.Second, + Timeout: defaultHttpTimeout, } log.AddContext(ctx).Infof("Try to login %s.", cli.url) @@ -199,7 +200,7 @@ func (cli *Client) ValidateLogin(ctx context.Context) error { } // Login try to login fusion storage by backend id -func (cli *Client) Login(ctx context.Context) error { +func (cli *RestClient) Login(ctx context.Context) error { var err error cli.client, err = newHTTPClientByBackendID(ctx, cli.backendID) if err != nil { @@ -256,7 +257,7 @@ func (cli *Client) Login(ctx context.Context) error { } // SetAccountId used to set account id of the client -func (cli *Client) SetAccountId(ctx context.Context) error { +func (cli *RestClient) SetAccountId(ctx context.Context) error { log.AddContext(ctx).Debugf("setAccountId start. account name: %s", cli.accountName) if cli.accountName == "" { cli.accountName = types.DefaultAccountName @@ -279,7 +280,7 @@ func (cli *Client) SetAccountId(ctx context.Context) error { } // Logout used to log out -func (cli *Client) Logout(ctx context.Context) { +func (cli *RestClient) Logout(ctx context.Context) { defer func() { cli.authToken = "" cli.client = nil @@ -305,26 +306,26 @@ func (cli *Client) Logout(ctx context.Context) { } // KeepAlive used to keep connection token alive -func (cli *Client) KeepAlive(ctx context.Context) { +func (cli *RestClient) KeepAlive(ctx context.Context) { _, err := cli.post(ctx, "/dsware/service/v1.3/sec/keepAlive", nil) if err != nil { log.AddContext(ctx).Warningf("Keep token alive error: %v", err) } } -func (cli *Client) reLoginLock(ctx context.Context) { +func (cli *RestClient) reLoginLock(ctx context.Context) { log.AddContext(ctx).Debugln("Try to reLoginLock.") cli.reloginMutex.Lock() log.AddContext(ctx).Debugln("ReLoginLock success.") } -func (cli *Client) reLoginUnlock(ctx context.Context) { +func (cli *RestClient) reLoginUnlock(ctx context.Context) { log.AddContext(ctx).Debugln("Try to reLoginUnlock.") cli.reloginMutex.Unlock() log.AddContext(ctx).Debugln("ReLoginUnlock success.") } -func (cli *Client) doCall(ctx context.Context, method string, url string, data map[string]any) ( +func (cli *RestClient) doCall(ctx context.Context, method string, url string, data map[string]any) ( http.Header, []byte, error) { var err error var reqUrl string @@ -382,7 +383,7 @@ func (cli *Client) doCall(ctx context.Context, method string, url string, data m return resp.Header, respBody, nil } -func (cli *Client) setRequestHeader(ctx context.Context, req *http.Request, url string) { +func (cli *RestClient) setRequestHeader(ctx context.Context, req *http.Request, url string) { req.Header.Set("Referer", cli.url) req.Header.Set("Content-Type", "application/json") @@ -401,7 +402,7 @@ func (cli *Client) setRequestHeader(ctx context.Context, req *http.Request, url } } -func (cli *Client) baseCall(ctx context.Context, method string, url string, data map[string]interface{}) (http.Header, +func (cli *RestClient) baseCall(ctx context.Context, method string, url string, data map[string]any) (http.Header, map[string]any, error) { var body map[string]any respHeader, respBody, err := cli.doCall(ctx, method, url, data) @@ -416,7 +417,7 @@ func (cli *Client) baseCall(ctx context.Context, method string, url string, data return respHeader, body, nil } -func (cli *Client) retryCall(ctx context.Context, method string, url string, data map[string]any) ( +func (cli *RestClient) retryCall(ctx context.Context, method string, url string, data map[string]any) ( http.Header, map[string]any, error) { log.AddContext(ctx).Debugf("retry call: method: %s, url: %s, data: %v.", method, url, data) @@ -444,7 +445,7 @@ func (cli *Client) retryCall(ctx context.Context, method string, url string, dat return respHeader, body, nil } -func (cli *Client) call(ctx context.Context, method string, url string, data map[string]any) ( +func (cli *RestClient) call(ctx context.Context, method string, url string, data map[string]any) ( http.Header, map[string]any, error) { var body map[string]any @@ -476,7 +477,7 @@ func (cli *Client) call(ctx context.Context, method string, url string, data map return respHeader, body, nil } -func (cli *Client) reLogin(ctx context.Context) error { +func (cli *RestClient) reLogin(ctx context.Context) error { cli.reLoginLock(ctx) defer cli.reLoginUnlock(ctx) @@ -497,7 +498,7 @@ func (cli *Client) reLogin(ctx context.Context) error { return nil } -func (cli *Client) get(ctx context.Context, +func (cli *RestClient) get(ctx context.Context, url string, data map[string]interface{}) (map[string]interface{}, error) { _, body, err := cli.call(ctx, "GET", url, data) @@ -505,32 +506,32 @@ func (cli *Client) get(ctx context.Context, } // Post used to send post request to storage client -func (cli *Client) Post(ctx context.Context, url string, data map[string]interface{}) (map[string]interface{}, error) { +func (cli *RestClient) Post(ctx context.Context, url string, data map[string]any) (map[string]any, error) { return cli.post(ctx, url, data) } -func (cli *Client) post(ctx context.Context, +func (cli *RestClient) post(ctx context.Context, url string, data map[string]interface{}) (map[string]interface{}, error) { _, body, err := cli.call(ctx, "POST", url, data) return body, err } -func (cli *Client) put(ctx context.Context, +func (cli *RestClient) put(ctx context.Context, url string, data map[string]interface{}) (map[string]interface{}, error) { _, body, err := cli.call(ctx, "PUT", url, data) return body, err } -func (cli *Client) delete(ctx context.Context, +func (cli *RestClient) delete(ctx context.Context, url string, data map[string]interface{}) (map[string]interface{}, error) { _, body, err := cli.call(ctx, "DELETE", url, data) return body, err } -func (cli *Client) checkErrorCode(ctx context.Context, resp map[string]interface{}, errorCode int64) bool { +func (cli *RestClient) checkErrorCode(ctx context.Context, resp map[string]interface{}, errorCode int64) bool { details, exist := resp["detail"].([]interface{}) if !exist || len(details) == 0 { return false @@ -590,19 +591,19 @@ func newHTTPClientByBackendID(ctx context.Context, backendID string) (*http.Clie jar, err := cookiejar.New(nil) if err != nil { log.AddContext(ctx).Errorf("create jar failed, error: %v", err) - return nil, err + return defaultHttpClient(), err } useCert, certMeta, err := pkgUtils.GetCertSecretFromBackendID(ctx, backendID) if err != nil { log.AddContext(ctx).Errorf("get cert secret from backend [%v] failed, error: %v", backendID, err) - return nil, err + return defaultHttpClient(), err } useCert, certPool, err := pkgUtils.GetCertPool(ctx, useCert, certMeta) if err != nil { log.AddContext(ctx).Errorf("get cert pool failed, error: %v", err) - return nil, err + return defaultHttpClient(), err } return &http.Client{ @@ -610,6 +611,14 @@ func newHTTPClientByBackendID(ctx context.Context, backendID string) (*http.Clie TLSClientConfig: &tls.Config{InsecureSkipVerify: !useCert, RootCAs: certPool}, }, Jar: jar, - Timeout: 60 * time.Second, + Timeout: defaultHttpTimeout, }, nil } + +func defaultHttpClient() *http.Client { + var defaultUseCert bool + return &http.Client{ + Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !defaultUseCert}}, + Timeout: defaultHttpTimeout, + } +} diff --git a/storage/fusionstorage/client/client_host.go b/storage/fusionstorage/client/client_host.go index 50d96d83..9ffc72df 100644 --- a/storage/fusionstorage/client/client_host.go +++ b/storage/fusionstorage/client/client_host.go @@ -29,7 +29,7 @@ const ( ) // GetHostByName used to get host by name -func (cli *Client) GetHostByName(ctx context.Context, hostName string) (map[string]interface{}, error) { +func (cli *RestClient) GetHostByName(ctx context.Context, hostName string) (map[string]interface{}, error) { data := map[string]interface{}{ "hostName": hostName, } @@ -66,7 +66,7 @@ func (cli *Client) GetHostByName(ctx context.Context, hostName string) (map[stri } // CreateHost used to create host -func (cli *Client) CreateHost(ctx context.Context, +func (cli *RestClient) CreateHost(ctx context.Context, hostName string, alua map[string]interface{}) error { data := map[string]interface{}{ @@ -97,7 +97,7 @@ func (cli *Client) CreateHost(ctx context.Context, } // UpdateHost used to update host -func (cli *Client) UpdateHost(ctx context.Context, hostName string, alua map[string]interface{}) error { +func (cli *RestClient) UpdateHost(ctx context.Context, hostName string, alua map[string]interface{}) error { data := map[string]interface{}{ "hostName": hostName, } @@ -124,7 +124,7 @@ func (cli *Client) UpdateHost(ctx context.Context, hostName string, alua map[str } // QueryHostByPort used query host by port -func (cli *Client) QueryHostByPort(ctx context.Context, port string) (string, error) { +func (cli *RestClient) QueryHostByPort(ctx context.Context, port string) (string, error) { data := map[string]interface{}{ "portName": []string{port}, } @@ -160,7 +160,7 @@ func (cli *Client) QueryHostByPort(ctx context.Context, port string) (string, er } // AddPortToHost used add port to host -func (cli *Client) AddPortToHost(ctx context.Context, initiatorName, hostName string) error { +func (cli *RestClient) AddPortToHost(ctx context.Context, initiatorName, hostName string) error { data := map[string]interface{}{ "hostName": hostName, "portNames": []string{initiatorName}, @@ -182,7 +182,7 @@ func (cli *Client) AddPortToHost(ctx context.Context, initiatorName, hostName st } // AddLunToHost usd to add lun to host -func (cli *Client) AddLunToHost(ctx context.Context, lunName, hostName string) error { +func (cli *RestClient) AddLunToHost(ctx context.Context, lunName, hostName string) error { data := map[string]interface{}{ "hostName": hostName, "lunNames": []string{lunName}, @@ -202,7 +202,7 @@ func (cli *Client) AddLunToHost(ctx context.Context, lunName, hostName string) e } // DeleteLunFromHost used to delete lun from host -func (cli *Client) DeleteLunFromHost(ctx context.Context, lunName, hostName string) error { +func (cli *RestClient) DeleteLunFromHost(ctx context.Context, lunName, hostName string) error { data := map[string]interface{}{ "hostName": hostName, "lunNames": []string{lunName}, @@ -222,7 +222,7 @@ func (cli *Client) DeleteLunFromHost(ctx context.Context, lunName, hostName stri } // QueryHostOfVolume used to query host of volume -func (cli *Client) QueryHostOfVolume(ctx context.Context, lunName string) ([]map[string]interface{}, error) { +func (cli *RestClient) QueryHostOfVolume(ctx context.Context, lunName string) ([]map[string]interface{}, error) { data := map[string]interface{}{ "lunName": lunName, } diff --git a/storage/fusionstorage/client/client_iscsi.go b/storage/fusionstorage/client/client_iscsi.go index bb63ddd4..b2c3c225 100644 --- a/storage/fusionstorage/client/client_iscsi.go +++ b/storage/fusionstorage/client/client_iscsi.go @@ -30,7 +30,7 @@ const ( ) // GetInitiatorByName used to get initiator by name -func (cli *Client) GetInitiatorByName(ctx context.Context, name string) (map[string]interface{}, error) { +func (cli *RestClient) GetInitiatorByName(ctx context.Context, name string) (map[string]interface{}, error) { data := map[string]interface{}{ "portName": name, } @@ -60,7 +60,7 @@ func (cli *Client) GetInitiatorByName(ctx context.Context, name string) (map[str } // CreateInitiator used to create initiator by name -func (cli *Client) CreateInitiator(ctx context.Context, name string) error { +func (cli *RestClient) CreateInitiator(ctx context.Context, name string) error { data := map[string]interface{}{ "portName": name, } @@ -81,7 +81,7 @@ func (cli *Client) CreateInitiator(ctx context.Context, name string) error { } // QueryIscsiPortal used to query iscsi portal -func (cli *Client) QueryIscsiPortal(ctx context.Context) ([]map[string]interface{}, error) { +func (cli *RestClient) QueryIscsiPortal(ctx context.Context) ([]map[string]interface{}, error) { data := make(map[string]interface{}) resp, err := cli.post(ctx, "/dsware/service/cluster/dswareclient/queryIscsiPortal", data) if err != nil { diff --git a/storage/fusionstorage/client/client_namespace.go b/storage/fusionstorage/client/client_namespace.go index c4cdfd5f..fe473317 100644 --- a/storage/fusionstorage/client/client_namespace.go +++ b/storage/fusionstorage/client/client_namespace.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,8 @@ const ( ) // CreateFileSystem used to create file system by params -func (cli *Client) CreateFileSystem(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { +func (cli *RestClient) CreateFileSystem(ctx context.Context, + params map[string]interface{}) (map[string]interface{}, error) { data := map[string]interface{}{ "name": params["name"].(string), "storage_pool_id": params["poolId"].(int64), @@ -85,7 +86,7 @@ func (cli *Client) CreateFileSystem(ctx context.Context, params map[string]inter } // DeleteFileSystem used to delete file system by id -func (cli *Client) DeleteFileSystem(ctx context.Context, id string) error { +func (cli *RestClient) DeleteFileSystem(ctx context.Context, id string) error { url := fmt.Sprintf("/api/v2/converged_service/namespaces/%s", id) resp, err := cli.delete(ctx, url, nil) if err != nil { @@ -108,7 +109,7 @@ func (cli *Client) DeleteFileSystem(ctx context.Context, id string) error { } // GetFileSystemByName used to get file system by name -func (cli *Client) GetFileSystemByName(ctx context.Context, name string) (map[string]interface{}, error) { +func (cli *RestClient) GetFileSystemByName(ctx context.Context, name string) (map[string]interface{}, error) { url := fmt.Sprintf("/api/v2/converged_service/namespaces?name=%s", name) resp, err := cli.get(ctx, url, nil) if err != nil { @@ -144,7 +145,7 @@ func (cli *Client) GetFileSystemByName(ctx context.Context, name string) (map[st } // CreateNfsShare used to create nfs share by params -func (cli *Client) CreateNfsShare(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { +func (cli *RestClient) CreateNfsShare(ctx context.Context, params map[string]any) (map[string]any, error) { data := map[string]interface{}{ "share_path": params["sharepath"].(string), "file_system_id": params["fsid"].(string), @@ -185,7 +186,7 @@ func (cli *Client) CreateNfsShare(ctx context.Context, params map[string]interfa } // DeleteNfsShare used to delete nfs share by id -func (cli *Client) DeleteNfsShare(ctx context.Context, id, accountId string) error { +func (cli *RestClient) DeleteNfsShare(ctx context.Context, id, accountId string) error { url := fmt.Sprintf("/api/v2/nas_protocol/nfs_share?id=%s&account_id=%s", id, accountId) resp, err := cli.delete(ctx, url, nil) if err != nil { @@ -209,7 +210,7 @@ func (cli *Client) DeleteNfsShare(ctx context.Context, id, accountId string) err } // GetNfsShareByPath used to get nfs share by path -func (cli *Client) GetNfsShareByPath(ctx context.Context, path, accountId string) (map[string]interface{}, error) { +func (cli *RestClient) GetNfsShareByPath(ctx context.Context, path, accountId string) (map[string]interface{}, error) { bytesPath, err := json.Marshal([]map[string]string{{"share_path": path}}) if err != nil { return nil, err @@ -269,7 +270,7 @@ type AllowNfsShareAccessRequest struct { } // AllowNfsShareAccess used for create nfs share client -func (cli *Client) AllowNfsShareAccess(ctx context.Context, req *AllowNfsShareAccessRequest) error { +func (cli *RestClient) AllowNfsShareAccess(ctx context.Context, req *AllowNfsShareAccessRequest) error { data := map[string]interface{}{ "access_name": req.AccessName, "share_id": req.ShareId, @@ -306,7 +307,7 @@ func (cli *Client) AllowNfsShareAccess(ctx context.Context, req *AllowNfsShareAc } // DeleteNfsShareAccess used to delete nfs share access by id -func (cli *Client) DeleteNfsShareAccess(ctx context.Context, accessID string) error { +func (cli *RestClient) DeleteNfsShareAccess(ctx context.Context, accessID string) error { url := fmt.Sprintf("/api/v2/nas_protocol/nfs_share_auth_client?id=%s", accessID) resp, err := cli.delete(ctx, url, nil) if err != nil { @@ -329,7 +330,7 @@ func (cli *Client) DeleteNfsShareAccess(ctx context.Context, accessID string) er } // GetNfsShareAccess used to get nfs share access by id -func (cli *Client) GetNfsShareAccess(ctx context.Context, shareID string) (map[string]interface{}, error) { +func (cli *RestClient) GetNfsShareAccess(ctx context.Context, shareID string) (map[string]interface{}, error) { url := fmt.Sprintf("/api/v2/nas_protocol/nfs_share_auth_client_list?filter=share_id::%s", shareID) resp, err := cli.get(ctx, url, nil) if err != nil { @@ -361,7 +362,7 @@ func (cli *Client) GetNfsShareAccess(ctx context.Context, shareID string) (map[s } // GetQuotaByFileSystemName query quota info by file system name -func (cli *Client) GetQuotaByFileSystemName(ctx context.Context, fsName string) (map[string]interface{}, error) { +func (cli *RestClient) GetQuotaByFileSystemName(ctx context.Context, fsName string) (map[string]interface{}, error) { fs, err := cli.GetFileSystemByName(ctx, fsName) if err != nil { log.AddContext(ctx).Errorf("Get filesystem %s error: %v", fsName, err) diff --git a/storage/fusionstorage/client/client_namespace_test.go b/storage/fusionstorage/client/client_namespace_test.go index d94c20f7..62be9889 100644 --- a/storage/fusionstorage/client/client_namespace_test.go +++ b/storage/fusionstorage/client/client_namespace_test.go @@ -21,8 +21,8 @@ import ( "reflect" "testing" - "bou.ke/monkey" - "github.com/smartystreets/goconvey/convey" + "github.com/agiledragon/gomonkey/v2" + "github.com/stretchr/testify/require" ) const ( @@ -30,15 +30,15 @@ const ( ) func TestAllowNfsShareAccess(t *testing.T) { - convey.Convey("Normal", t, func() { - guard := monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "Post", - func(_ *Client, _ context.Context, _ string, _ map[string]interface{}) (map[string]interface{}, error) { + t.Run("Normal", func(t *testing.T) { + guard := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "Post", + func(_ *RestClient, _ context.Context, _ string, _ map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "data": map[string]interface{}{}, "result": map[string]interface{}{"code": float64(0), "description": ""}, }, nil }) - defer guard.Unpatch() + defer guard.Reset() err := testClient.AllowNfsShareAccess(context.TODO(), &AllowNfsShareAccessRequest{ AccessName: "test", @@ -48,17 +48,17 @@ func TestAllowNfsShareAccess(t *testing.T) { RootSquash: 1, AccountId: "0", }) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) - convey.Convey("Result Code Not Exist", t, func() { - guard := monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "Post", - func(_ *Client, _ context.Context, _ string, _ map[string]interface{}) (map[string]interface{}, error) { + t.Run("Result Code Not Exist", func(t *testing.T) { + guard := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "Post", + func(_ *RestClient, _ context.Context, _ string, _ map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "data": map[string]interface{}{}, }, nil }) - defer guard.Unpatch() + defer guard.Reset() err := testClient.AllowNfsShareAccess(context.TODO(), &AllowNfsShareAccessRequest{ AccessName: "test", @@ -68,18 +68,18 @@ func TestAllowNfsShareAccess(t *testing.T) { RootSquash: 1, AccountId: "0", }) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) - convey.Convey("Client Already Exist", t, func() { - guard := monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "Post", - func(_ *Client, _ context.Context, _ string, _ map[string]interface{}) (map[string]interface{}, error) { + t.Run("RestClient Already Exist", func(t *testing.T) { + guard := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "Post", + func(_ *RestClient, _ context.Context, _ string, _ map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "data": map[string]interface{}{}, "result": map[string]interface{}{"code": float64(clientAlreadyExist), "description": ""}, }, nil }) - defer guard.Unpatch() + defer guard.Reset() err := testClient.AllowNfsShareAccess(context.TODO(), &AllowNfsShareAccessRequest{ AccessName: "test", @@ -89,18 +89,18 @@ func TestAllowNfsShareAccess(t *testing.T) { RootSquash: 1, AccountId: "0", }) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) - convey.Convey("Error code is not zero", t, func() { - guard := monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "Post", - func(_ *Client, _ context.Context, _ string, _ map[string]interface{}) (map[string]interface{}, error) { + t.Run("Error code is not zero", func(t *testing.T) { + guard := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "Post", + func(_ *RestClient, _ context.Context, _ string, _ map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "data": map[string]interface{}{}, "result": map[string]interface{}{"code": float64(100), "description": ""}, }, nil }) - defer guard.Unpatch() + defer guard.Reset() err := testClient.AllowNfsShareAccess(context.TODO(), &AllowNfsShareAccessRequest{ AccessName: "test", @@ -110,6 +110,6 @@ func TestAllowNfsShareAccess(t *testing.T) { RootSquash: 1, AccountId: "0", }) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } diff --git a/storage/fusionstorage/client/client_qos.go b/storage/fusionstorage/client/client_qos.go index 4deb1b56..760eeeab 100644 --- a/storage/fusionstorage/client/client_qos.go +++ b/storage/fusionstorage/client/client_qos.go @@ -29,7 +29,7 @@ import ( ) // GetConvergedQoSNameByID used to get qos name by id -func (cli *Client) GetConvergedQoSNameByID(ctx context.Context, qosId int) (string, error) { +func (cli *RestClient) GetConvergedQoSNameByID(ctx context.Context, qosId int) (string, error) { url := fmt.Sprintf("/api/v2/dros_service/converged_qos_policy?qos_scale=%d&id=%d", types.QosScaleNamespace, qosId) resp, err := cli.get(ctx, url, nil) @@ -55,7 +55,7 @@ func (cli *Client) GetConvergedQoSNameByID(ctx context.Context, qosId int) (stri } // CreateConvergedQoS used to create converged QoS -func (cli *Client) CreateConvergedQoS(ctx context.Context, req *types.CreateConvergedQoSReq) (int, error) { +func (cli *RestClient) CreateConvergedQoS(ctx context.Context, req *types.CreateConvergedQoSReq) (int, error) { data := map[string]interface{}{ "account_id": cli.accountId, "qos_scale": req.QosScale, @@ -88,7 +88,7 @@ func (cli *Client) CreateConvergedQoS(ctx context.Context, req *types.CreateConv } // DeleteConvergedQoS used to delete converged QoS by name -func (cli *Client) DeleteConvergedQoS(ctx context.Context, qosName string) error { +func (cli *RestClient) DeleteConvergedQoS(ctx context.Context, qosName string) error { url := fmt.Sprintf("/api/v2/dros_service/converged_qos_policy?qos_scale=%d&name=%s", types.QosScaleNamespace, qosName) resp, err := cli.delete(ctx, url, nil) @@ -100,7 +100,7 @@ func (cli *Client) DeleteConvergedQoS(ctx context.Context, qosName string) error } // CreateQoS used to create QoS -func (cli *Client) CreateQoS(ctx context.Context, qosName string, qosData map[string]int) error { +func (cli *RestClient) CreateQoS(ctx context.Context, qosName string, qosData map[string]int) error { data := map[string]interface{}{ "qosName": qosName, "qosSpecInfo": qosData, @@ -121,7 +121,7 @@ func (cli *Client) CreateQoS(ctx context.Context, qosName string, qosData map[st } // DeleteQoS used to delete QoS by name -func (cli *Client) DeleteQoS(ctx context.Context, qosName string) error { +func (cli *RestClient) DeleteQoS(ctx context.Context, qosName string) error { data := map[string]interface{}{ "qosNames": []string{qosName}, } @@ -141,7 +141,7 @@ func (cli *Client) DeleteQoS(ctx context.Context, qosName string) error { } // DisassociateConvergedQoSWithVolume used to delete a converged QoS policy association -func (cli *Client) DisassociateConvergedQoSWithVolume(ctx context.Context, objectName string) error { +func (cli *RestClient) DisassociateConvergedQoSWithVolume(ctx context.Context, objectName string) error { url := fmt.Sprintf("/api/v2/dros_service/converged_qos_association?qos_scale=%d&object_name=%s", types.QosScaleNamespace, objectName) resp, err := cli.delete(ctx, url, nil) @@ -153,7 +153,7 @@ func (cli *Client) DisassociateConvergedQoSWithVolume(ctx context.Context, objec } // AssociateConvergedQoSWithVolume used to add a converged QoS policy association -func (cli *Client) AssociateConvergedQoSWithVolume(ctx context.Context, +func (cli *RestClient) AssociateConvergedQoSWithVolume(ctx context.Context, req *types.AssociateConvergedQoSWithVolumeReq) error { data := map[string]interface{}{ @@ -172,7 +172,7 @@ func (cli *Client) AssociateConvergedQoSWithVolume(ctx context.Context, } // AssociateQoSWithVolume used to associate QoS with volume -func (cli *Client) AssociateQoSWithVolume(ctx context.Context, volName, qosName string) error { +func (cli *RestClient) AssociateQoSWithVolume(ctx context.Context, volName, qosName string) error { data := map[string]interface{}{ "keyNames": []string{volName}, "qosName": qosName, @@ -193,7 +193,7 @@ func (cli *Client) AssociateQoSWithVolume(ctx context.Context, volName, qosName } // DisassociateQoSWithVolume used to disassociate QoS with volume -func (cli *Client) DisassociateQoSWithVolume(ctx context.Context, volName, qosName string) error { +func (cli *RestClient) DisassociateQoSWithVolume(ctx context.Context, volName, qosName string) error { data := map[string]interface{}{ "keyNames": []string{volName}, "qosName": qosName, @@ -214,7 +214,7 @@ func (cli *Client) DisassociateQoSWithVolume(ctx context.Context, volName, qosNa } // GetQoSPolicyAssociationCount used to get count of qos association -func (cli *Client) GetQoSPolicyAssociationCount(ctx context.Context, qosPolicyId int) (int, error) { +func (cli *RestClient) GetQoSPolicyAssociationCount(ctx context.Context, qosPolicyId int) (int, error) { filterRaw := fmt.Sprintf("{\"qos_policy_id\":\"%d\",\"qos_scale\":\"%d\",\"account_id\":\"%d\"}", qosPolicyId, types.QosScaleNamespace, cli.accountId) url := fmt.Sprintf("/api/v2/dros_service/converged_qos_association_count?filter=%s", @@ -245,7 +245,7 @@ func (cli *Client) GetQoSPolicyAssociationCount(ctx context.Context, qosPolicyId } // GetQoSPolicyIdByFsName used to get qos id by fs name -func (cli *Client) GetQoSPolicyIdByFsName(ctx context.Context, namespaceName string) (int, error) { +func (cli *RestClient) GetQoSPolicyIdByFsName(ctx context.Context, namespaceName string) (int, error) { rangeRaw := "{\"offset\":0,\"limit\":100}" filterRaw := fmt.Sprintf("{\"object_name\":\"%s\",\"qos_scale\":\"0\",\"account_id\":\"%d\"}", namespaceName, cli.accountId) @@ -285,7 +285,7 @@ func (cli *Client) GetQoSPolicyIdByFsName(ctx context.Context, namespaceName str } // GetQoSNameByVolume used to get QoS name by volume name -func (cli *Client) GetQoSNameByVolume(ctx context.Context, volName string) (string, error) { +func (cli *RestClient) GetQoSNameByVolume(ctx context.Context, volName string) (string, error) { url := fmt.Sprintf("/dsware/service/v1.3/volume/qos?volName=%s", volName) resp, err := cli.get(ctx, url, nil) if err != nil { @@ -307,7 +307,7 @@ func (cli *Client) GetQoSNameByVolume(ctx context.Context, volName string) (stri } // GetAssociateCountOfQoS used to get associate count of QoS -func (cli *Client) GetAssociateCountOfQoS(ctx context.Context, qosName string) (int, error) { +func (cli *RestClient) GetAssociateCountOfQoS(ctx context.Context, qosName string) (int, error) { storagePools, err := cli.getAllPools(ctx) if err != nil { return 0, err @@ -360,7 +360,7 @@ func (cli *Client) GetAssociateCountOfQoS(ctx context.Context, qosName string) ( return 0, nil } -func (cli *Client) getAssociateObjOfQoS(ctx context.Context, +func (cli *RestClient) getAssociateObjOfQoS(ctx context.Context, qosName, objType string, poolId int64) (map[string]interface{}, error) { data := map[string]interface{}{ @@ -382,7 +382,7 @@ func (cli *Client) getAssociateObjOfQoS(ctx context.Context, return resp, nil } -func (cli *Client) getAssociatePoolOfQoS(ctx context.Context, qosName string) (map[string]interface{}, error) { +func (cli *RestClient) getAssociatePoolOfQoS(ctx context.Context, qosName string) (map[string]interface{}, error) { data := map[string]interface{}{ "qosName": qosName, } diff --git a/storage/fusionstorage/client/client_quota.go b/storage/fusionstorage/client/client_quota.go index 40e4b0bb..23319415 100644 --- a/storage/fusionstorage/client/client_quota.go +++ b/storage/fusionstorage/client/client_quota.go @@ -29,7 +29,7 @@ const ( ) // CreateQuota creates quota by params -func (cli *Client) CreateQuota(ctx context.Context, params map[string]interface{}) error { +func (cli *RestClient) CreateQuota(ctx context.Context, params map[string]interface{}) error { resp, err := cli.post(ctx, "/api/v2/file_service/fs_quota", params) if err != nil { return err @@ -52,7 +52,7 @@ func (cli *Client) CreateQuota(ctx context.Context, params map[string]interface{ } // UpdateQuota updates quota by params -func (cli *Client) UpdateQuota(ctx context.Context, params map[string]interface{}) error { +func (cli *RestClient) UpdateQuota(ctx context.Context, params map[string]interface{}) error { resp, err := cli.put(ctx, "/api/v2/file_service/fs_quota", params) if err != nil { return err @@ -73,7 +73,7 @@ func (cli *Client) UpdateQuota(ctx context.Context, params map[string]interface{ } // GetQuotaByFileSystemById query quota info by file system id -func (cli *Client) GetQuotaByFileSystemById(ctx context.Context, fsID string) (map[string]interface{}, error) { +func (cli *RestClient) GetQuotaByFileSystemById(ctx context.Context, fsID string) (map[string]interface{}, error) { url := "/api/v2/file_service/fs_quota?parent_type=40&parent_id=" + fsID + "&range=%7B%22offset%22%3A0%2C%22limit%22%3A100%7D" resp, err := cli.get(ctx, url, nil) @@ -110,7 +110,7 @@ func (cli *Client) GetQuotaByFileSystemById(ctx context.Context, fsID string) (m } // DeleteQuota deletes quota by id -func (cli *Client) DeleteQuota(ctx context.Context, quotaID string) error { +func (cli *RestClient) DeleteQuota(ctx context.Context, quotaID string) error { url := fmt.Sprintf("/api/v2/file_service/fs_quota/%s", quotaID) resp, err := cli.delete(ctx, url, nil) if err != nil { diff --git a/storage/fusionstorage/client/client_snapshot.go b/storage/fusionstorage/client/client_snapshot.go index 5a2fcc75..55ef1302 100644 --- a/storage/fusionstorage/client/client_snapshot.go +++ b/storage/fusionstorage/client/client_snapshot.go @@ -28,7 +28,7 @@ const ( ) // CreateSnapshot creates volume snapshot -func (cli *Client) CreateSnapshot(ctx context.Context, snapshotName, volName string) error { +func (cli *RestClient) CreateSnapshot(ctx context.Context, snapshotName, volName string) error { data := map[string]interface{}{ "volName": volName, "snapshotName": snapshotName, @@ -48,7 +48,7 @@ func (cli *Client) CreateSnapshot(ctx context.Context, snapshotName, volName str } // DeleteSnapshot deletes volume snapshot -func (cli *Client) DeleteSnapshot(ctx context.Context, snapshotName string) error { +func (cli *RestClient) DeleteSnapshot(ctx context.Context, snapshotName string) error { data := map[string]interface{}{ "snapshotName": snapshotName, } @@ -67,7 +67,7 @@ func (cli *Client) DeleteSnapshot(ctx context.Context, snapshotName string) erro } // GetSnapshotByName get snapshot by name -func (cli *Client) GetSnapshotByName(ctx context.Context, snapshotName string) (map[string]interface{}, error) { +func (cli *RestClient) GetSnapshotByName(ctx context.Context, snapshotName string) (map[string]interface{}, error) { url := fmt.Sprintf("/dsware/service/v1.3/snapshot/queryByName?snapshotName=%s", snapshotName) resp, err := cli.get(ctx, url, nil) if err != nil { @@ -94,7 +94,7 @@ func (cli *Client) GetSnapshotByName(ctx context.Context, snapshotName string) ( } // CreateVolumeFromSnapshot creates volume from snapshot -func (cli *Client) CreateVolumeFromSnapshot(ctx context.Context, +func (cli *RestClient) CreateVolumeFromSnapshot(ctx context.Context, volName string, volSize int64, snapshotName string) error { diff --git a/storage/fusionstorage/client/client_system.go b/storage/fusionstorage/client/client_system.go index 898ba241..d565fdb2 100644 --- a/storage/fusionstorage/client/client_system.go +++ b/storage/fusionstorage/client/client_system.go @@ -28,7 +28,7 @@ import ( ) // GetAccountIdByName gets account id by account name -func (cli *Client) GetAccountIdByName(ctx context.Context, accountName string) (string, error) { +func (cli *RestClient) GetAccountIdByName(ctx context.Context, accountName string) (string, error) { url := fmt.Sprintf("/dfv/service/obsPOE/query_accounts?name=%s", accountName) resp, err := cli.get(ctx, url, nil) if err != nil { @@ -53,7 +53,7 @@ func (cli *Client) GetAccountIdByName(ctx context.Context, accountName string) ( } // GetPoolByName gets pool by pool name -func (cli *Client) GetPoolByName(ctx context.Context, poolName string) (map[string]interface{}, error) { +func (cli *RestClient) GetPoolByName(ctx context.Context, poolName string) (map[string]interface{}, error) { resp, err := cli.get(ctx, "/dsware/service/v1.3/storagePool", nil) if err != nil { return nil, err @@ -86,7 +86,7 @@ func (cli *Client) GetPoolByName(ctx context.Context, poolName string) (map[stri } // GetPoolById gets pool by pool id -func (cli *Client) GetPoolById(ctx context.Context, poolId int64) (map[string]interface{}, error) { +func (cli *RestClient) GetPoolById(ctx context.Context, poolId int64) (map[string]interface{}, error) { url := fmt.Sprintf("/dsware/service/v1.3/storagePool?poolId=%d", poolId) resp, err := cli.get(ctx, url, nil) if err != nil { @@ -120,7 +120,7 @@ func (cli *Client) GetPoolById(ctx context.Context, poolId int64) (map[string]in } // GetAllAccounts gets all accounts -func (cli *Client) GetAllAccounts(ctx context.Context) ([]string, error) { +func (cli *RestClient) GetAllAccounts(ctx context.Context) ([]string, error) { resp, err := cli.get(ctx, "/dfv/service/obsPOE/accounts", nil) if err != nil { return nil, err @@ -154,7 +154,7 @@ func (cli *Client) GetAllAccounts(ctx context.Context) ([]string, error) { } // GetAllPools gets all pools -func (cli *Client) GetAllPools(ctx context.Context) (map[string]interface{}, error) { +func (cli *RestClient) GetAllPools(ctx context.Context) (map[string]interface{}, error) { resp, err := cli.get(ctx, "/dsware/service/v1.3/storagePool", nil) if err != nil { return nil, err @@ -189,7 +189,7 @@ func (cli *Client) GetAllPools(ctx context.Context) (map[string]interface{}, err return pools, nil } -func (cli *Client) getAllPools(ctx context.Context) ([]interface{}, error) { +func (cli *RestClient) getAllPools(ctx context.Context) ([]interface{}, error) { resp, err := cli.get(ctx, "/dsware/service/v1.3/storagePool", nil) if err != nil { return nil, err @@ -208,7 +208,7 @@ func (cli *Client) getAllPools(ctx context.Context) ([]interface{}, error) { } // GetNFSServiceSetting gets nfs service settings -func (cli *Client) GetNFSServiceSetting(ctx context.Context) (map[string]bool, error) { +func (cli *RestClient) GetNFSServiceSetting(ctx context.Context) (map[string]bool, error) { setting := map[string]bool{"SupportNFS41": false} req := make(map[string]interface{}) diff --git a/storage/fusionstorage/client/client_test.go b/storage/fusionstorage/client/client_test.go index b1aa14d3..88047c4b 100644 --- a/storage/fusionstorage/client/client_test.go +++ b/storage/fusionstorage/client/client_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/prashantv/gostub" - "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" "huawei-csi-driver/csi/app" cfg "huawei-csi-driver/csi/app/config" @@ -29,7 +29,7 @@ import ( ) var ( - testClient *Client + testClient *RestClient ) func TestMain(m *testing.M) { @@ -56,27 +56,27 @@ func TestMain(m *testing.M) { } func TestGetErrorCode(t *testing.T) { - convey.Convey("Normal case", t, func() { + t.Run("Normal case", func(t *testing.T) { errCode, err := getErrorCode(map[string]any{ "name": "mock-name", "errorCode": 12345.0, "suggestion": "mock-suggestion", }) - convey.So(errCode, convey.ShouldEqual, 12345) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, 12345, errCode) }) - convey.Convey("Error code is string ", t, func() { + t.Run("Error code is string ", func(t *testing.T) { errCode, err := getErrorCode(map[string]any{ "name": "mock-name", "errorCode": "12345", "suggestion": "mock-suggestion", }) - convey.So(errCode, convey.ShouldEqual, 12345) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, 12345, errCode) }) - convey.Convey("Error code in result", t, func() { + t.Run("Error code in result", func(t *testing.T) { errCode, err := getErrorCode(map[string]any{ "result": map[string]any{ "code": 12345.0, @@ -85,11 +85,11 @@ func TestGetErrorCode(t *testing.T) { "name": "mock-name", }, }) - convey.So(errCode, convey.ShouldEqual, 12345) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) + require.Equal(t, 12345, errCode) }) - convey.Convey("Can not convert to int", t, func() { + t.Run("Can not convert to int", func(t *testing.T) { _, err := getErrorCode(map[string]any{ "result": map[string]any{ "code": "a12345", @@ -98,6 +98,6 @@ func TestGetErrorCode(t *testing.T) { "name": "mock-name", }, }) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } diff --git a/storage/fusionstorage/client/client_volume.go b/storage/fusionstorage/client/client_volume.go index 31404e1b..e3924ce6 100644 --- a/storage/fusionstorage/client/client_volume.go +++ b/storage/fusionstorage/client/client_volume.go @@ -32,7 +32,7 @@ const ( ) // CreateVolume creates volume by params -func (cli *Client) CreateVolume(ctx context.Context, params map[string]interface{}) error { +func (cli *RestClient) CreateVolume(ctx context.Context, params map[string]interface{}) error { data := map[string]interface{}{ "volName": params["name"].(string), "volSize": params["capacity"].(int64), @@ -54,7 +54,7 @@ func (cli *Client) CreateVolume(ctx context.Context, params map[string]interface } // GetVolumeByName gets volume info by name -func (cli *Client) GetVolumeByName(ctx context.Context, name string) (map[string]interface{}, error) { +func (cli *RestClient) GetVolumeByName(ctx context.Context, name string) (map[string]interface{}, error) { url := fmt.Sprintf("/dsware/service/v1.3/volume/queryByName?volName=%s", name) resp, err := cli.get(ctx, url, nil) if err != nil { @@ -87,7 +87,7 @@ func (cli *Client) GetVolumeByName(ctx context.Context, name string) (map[string } // DeleteVolume deletes volume by name -func (cli *Client) DeleteVolume(ctx context.Context, name string) error { +func (cli *RestClient) DeleteVolume(ctx context.Context, name string) error { data := map[string]interface{}{ "volNames": []string{name}, } @@ -139,7 +139,7 @@ func (cli *Client) DeleteVolume(ctx context.Context, name string) error { } // AttachVolume attaches volume target ip -func (cli *Client) AttachVolume(ctx context.Context, name, ip string) error { +func (cli *RestClient) AttachVolume(ctx context.Context, name, ip string) error { data := map[string]interface{}{ "volName": []string{name}, "ipList": []string{ip}, @@ -169,7 +169,7 @@ func (cli *Client) AttachVolume(ctx context.Context, name, ip string) error { } // DetachVolume detaches volume from target ip -func (cli *Client) DetachVolume(ctx context.Context, name, ip string) error { +func (cli *RestClient) DetachVolume(ctx context.Context, name, ip string) error { data := map[string]interface{}{ "volName": []string{name}, "ipList": []string{ip}, @@ -199,7 +199,7 @@ func (cli *Client) DetachVolume(ctx context.Context, name, ip string) error { } // ExtendVolume extends volume capacity -func (cli *Client) ExtendVolume(ctx context.Context, lunName string, newCapacity int64) error { +func (cli *RestClient) ExtendVolume(ctx context.Context, lunName string, newCapacity int64) error { data := map[string]interface{}{ "volName": lunName, "newVolSize": newCapacity, @@ -219,7 +219,7 @@ func (cli *Client) ExtendVolume(ctx context.Context, lunName string, newCapacity } // GetHostLunId gets host lun id of hostName -func (cli *Client) GetHostLunId(ctx context.Context, hostName, lunName string) (string, error) { +func (cli *RestClient) GetHostLunId(ctx context.Context, hostName, lunName string) (string, error) { data := map[string]interface{}{ "hostName": hostName, } diff --git a/storage/fusionstorage/smartx/smartx.go b/storage/fusionstorage/smartx/smartx.go index ae06b55e..b2cad7bc 100644 --- a/storage/fusionstorage/smartx/smartx.go +++ b/storage/fusionstorage/smartx/smartx.go @@ -71,11 +71,11 @@ func VerifyQos(ctx context.Context, qosConfig string) (map[string]int, error) { // QoS provides qos client type QoS struct { - cli *client.Client + cli *client.RestClient } // NewQoS inits a new qos client -func NewQoS(cli *client.Client) *QoS { +func NewQoS(cli *client.RestClient) *QoS { return &QoS{ cli: cli, } diff --git a/storage/fusionstorage/volume/nas.go b/storage/fusionstorage/volume/nas.go index cb0f82fd..8b6ce26e 100644 --- a/storage/fusionstorage/volume/nas.go +++ b/storage/fusionstorage/volume/nas.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,8 +33,8 @@ import ( "huawei-csi-driver/storage/fusionstorage/types" fsUtils "huawei-csi-driver/storage/fusionstorage/utils" "huawei-csi-driver/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" ) const ( @@ -44,6 +44,8 @@ const ( quotaParentFileSystem = "40" directoryQuotaType = "1" quotaInvalidValue = 18446744073709552000 + waitUntilTimeout = 6 * time.Hour + waitUntilInterval = 5 * time.Second ) const ( @@ -61,11 +63,11 @@ const ( // NAS provides nas storage client type NAS struct { - cli *client.Client + cli *client.RestClient } // NewNAS inits a new nas client -func NewNAS(cli *client.Client) *NAS { +func NewNAS(cli *client.RestClient) *NAS { return &NAS{ cli: cli, } @@ -289,7 +291,7 @@ func (p *NAS) Create(ctx context.Context, params map[string]interface{}) (utils. return nil, err } - createTask := taskflow.NewTaskFlow(ctx, "Create-FileSystem-Volume") + createTask := flow.NewTaskFlow(ctx, "Create-FileSystem-Volume") createTask.AddTask("Create-FS", p.createFS, p.revertFS) createTask.AddTask("Create-Quota", p.createQuota, p.revertQuota) createTask.AddTask("Create-Converged-QoS", p.createConvergedQoS, p.revertConvergedQoS) @@ -611,7 +613,7 @@ func (p *NAS) waitFilesystemCreated(ctx context.Context, fsName string) error { } else { return false, nil } - }, time.Hour*6, time.Second*5) + }, waitUntilTimeout, waitUntilInterval) return err } diff --git a/storage/fusionstorage/volume/nas_test.go b/storage/fusionstorage/volume/nas_test.go index 8d2cc003..2b845aaf 100644 --- a/storage/fusionstorage/volume/nas_test.go +++ b/storage/fusionstorage/volume/nas_test.go @@ -22,9 +22,8 @@ import ( "reflect" "testing" - "bou.ke/monkey" "github.com/agiledragon/gomonkey/v2" - "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" "huawei-csi-driver/storage/fusionstorage/client" "huawei-csi-driver/storage/fusionstorage/types" @@ -35,7 +34,7 @@ const ( logName = "expandTest.log" ) -var testClient *client.Client +var testClient *client.RestClient var ctx context.Context func TestMain(m *testing.M) { @@ -48,10 +47,10 @@ func TestMain(m *testing.M) { } func TestPreCreate(t *testing.T) { - convey.Convey("Normal", t, func() { + t.Run("Normal", func(t *testing.T) { m := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetPoolByName", - func(_ *client.Client, ctx context.Context, poolName string) (map[string]interface{}, error) { + func(_ *client.RestClient, ctx context.Context, poolName string) (map[string]interface{}, error) { return map[string]interface{}{"mock": "mock"}, nil }) defer m.Reset() @@ -61,21 +60,21 @@ func TestPreCreate(t *testing.T) { "authclient": "*", "name": "mock-name", }) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) - convey.Convey("Auth client empty", t, func() { + t.Run("Auth client empty", func(t *testing.T) { nas := NewNAS(testClient) err := nas.preCreate(context.TODO(), map[string]interface{}{ "name": "mock-name", }) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) - convey.Convey("Name is empty", t, func() { + t.Run("Name is empty", func(t *testing.T) { m := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetPoolByName", - func(_ *client.Client, ctx context.Context, poolName string) (map[string]interface{}, error) { + func(_ *client.RestClient, ctx context.Context, poolName string) (map[string]interface{}, error) { return map[string]interface{}{"mock": "mock"}, nil }) defer m.Reset() @@ -84,28 +83,26 @@ func TestPreCreate(t *testing.T) { err := nas.preCreate(context.TODO(), map[string]interface{}{ "authclient": "*", }) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestExpandWithNormal(t *testing.T) { - convey.Convey("Normal", t, func() { - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetFileSystemByName", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + t.Run("Normal", func(t *testing.T) { + p := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetFileSystemByName", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": float64(522), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + }).ApplyMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": "522@2", "space_hard_quota": float64(2147483648), "space_soft_quota": float64(18446744073709551615), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "UpdateQuota", - func(_ *client.Client, _ context.Context, param map[string]interface{}) error { + }).ApplyMethod(reflect.TypeOf(testClient), "UpdateQuota", + func(_ *client.RestClient, _ context.Context, param map[string]interface{}) error { _, exitId := param["id"] _, exitHardQuota := param["space_hard_quota"] _, exitSoftQuota := param["space_soft_quota"] @@ -114,28 +111,28 @@ func TestExpandWithNormal(t *testing.T) { } return errors.New("fail") }) + defer p.Reset() + nas := NewNAS(testClient) err := nas.Expand(context.TODO(), "123", 3221225472) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) } func TestExpandWithFileSystemNotExit(t *testing.T) { - convey.Convey("File System Not Exist", t, func() { - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetFileSystemByName", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + t.Run("File System Not Exist", func(t *testing.T) { + p := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetFileSystemByName", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return nil, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + }).ApplyMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": "522@2", "space_hard_quota": float64(2147483648), "space_soft_quota": float64(18446744073709551615), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "UpdateQuota", - func(_ *client.Client, _ context.Context, param map[string]interface{}) error { + }).ApplyMethod(reflect.TypeOf(testClient), "UpdateQuota", + func(_ *client.RestClient, _ context.Context, param map[string]interface{}) error { _, exitId := param["id"] _, exitHardQuota := param["space_hard_quota"] _, exitSoftQuota := param["space_soft_quota"] @@ -144,29 +141,29 @@ func TestExpandWithFileSystemNotExit(t *testing.T) { } return errors.New("fail") }) + defer p.Reset() + nas := NewNAS(testClient) err := nas.Expand(context.TODO(), "123", 3221225472) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestExpandWithQuotaIdNotExist(t *testing.T) { - convey.Convey("Quota Id Not Exist", t, func() { - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetFileSystemByName", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + t.Run("Quota Id Not Exist", func(t *testing.T) { + p := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetFileSystemByName", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": float64(522), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + }).ApplyMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "space_hard_quota": float64(2147483648), "space_soft_quota": float64(18446744073709551615), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "UpdateQuota", - func(_ *client.Client, _ context.Context, param map[string]interface{}) error { + }).ApplyMethod(reflect.TypeOf(testClient), "UpdateQuota", + func(_ *client.RestClient, _ context.Context, param map[string]interface{}) error { _, exitId := param["id"] _, exitHardQuota := param["space_hard_quota"] _, exitSoftQuota := param["space_soft_quota"] @@ -175,28 +172,28 @@ func TestExpandWithQuotaIdNotExist(t *testing.T) { } return errors.New("fail") }) + defer p.Reset() + nas := NewNAS(testClient) err := nas.Expand(context.TODO(), "123", 3221225472) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestExpandWhenHardQuotaOrSoftQuotaNotExist(t *testing.T) { - convey.Convey("space_hard_quota or space_soft_quota Not Exist", t, func() { - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetFileSystemByName", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + t.Run("space_hard_quota or space_soft_quota Not Exist", func(t *testing.T) { + p := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetFileSystemByName", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": float64(522), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + }).ApplyMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": "522@2", }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "UpdateQuota", - func(_ *client.Client, _ context.Context, param map[string]interface{}) error { + }).ApplyMethod(reflect.TypeOf(testClient), "UpdateQuota", + func(_ *client.RestClient, _ context.Context, param map[string]interface{}) error { _, exitId := param["id"] _, exitHardQuota := param["space_hard_quota"] _, exitSoftQuota := param["space_soft_quota"] @@ -205,30 +202,29 @@ func TestExpandWhenHardQuotaOrSoftQuotaNotExist(t *testing.T) { } return errors.New("fail") }) + defer p.Reset() + nas := NewNAS(testClient) err := nas.Expand(context.TODO(), "123", 3221225472) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestExpandWhenHardQuotaNotExist(t *testing.T) { - convey.Convey("Hard Quota Not Exist", t, func() { - - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetFileSystemByName", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + t.Run("Hard Quota Not Exist", func(t *testing.T) { + p := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetFileSystemByName", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": float64(522), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + }).ApplyMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": "522@2", "space_hard_quota": float64(18446744073709551615), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "UpdateQuota", - func(_ *client.Client, _ context.Context, param map[string]interface{}) error { + }).ApplyMethod(reflect.TypeOf(testClient), "UpdateQuota", + func(_ *client.RestClient, _ context.Context, param map[string]interface{}) error { _, exitId := param["id"] _, exitHardQuota := param["space_hard_quota"] _, exitSoftQuota := param["space_soft_quota"] @@ -237,30 +233,29 @@ func TestExpandWhenHardQuotaNotExist(t *testing.T) { } return errors.New("fail") }) + defer p.Reset() + nas := NewNAS(testClient) err := nas.Expand(context.TODO(), "123", 3221225472) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestExpandWithSoftQuotaNotExist(t *testing.T) { - convey.Convey("Soft Quota Not Exist", t, func() { - - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetFileSystemByName", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + t.Run("Soft Quota Not Exist", func(t *testing.T) { + p := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetFileSystemByName", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": float64(522), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + }).ApplyMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": "522@2", "space_soft_quota": float64(18446744073709551615), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "UpdateQuota", - func(_ *client.Client, _ context.Context, param map[string]interface{}) error { + }).ApplyMethod(reflect.TypeOf(testClient), "UpdateQuota", + func(_ *client.RestClient, _ context.Context, param map[string]interface{}) error { _, exitId := param["id"] _, exitHardQuota := param["space_hard_quota"] _, exitSoftQuota := param["space_soft_quota"] @@ -269,108 +264,111 @@ func TestExpandWithSoftQuotaNotExist(t *testing.T) { } return errors.New("fail") }) + defer p.Reset() + nas := NewNAS(testClient) err := nas.Expand(context.TODO(), "123", 3221225472) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestExpandWithUpdateQuotaFail(t *testing.T) { - - convey.Convey("Update Quota Fail", t, func() { - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetFileSystemByName", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + t.Run("Update Quota Fail", func(t *testing.T) { + p := gomonkey.ApplyMethod(reflect.TypeOf(testClient), "GetFileSystemByName", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": float64(522), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", - func(_ *client.Client, _ context.Context, _ string) (map[string]interface{}, error) { + }).ApplyMethod(reflect.TypeOf(testClient), "GetQuotaByFileSystemById", + func(_ *client.RestClient, _ context.Context, _ string) (map[string]interface{}, error) { return map[string]interface{}{ "id": "522@2", "space_hard_quota": float64(2147483648), "space_soft_quota": float64(18446744073709551615), }, nil - }) - _ = monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "UpdateQuota", - func(_ *client.Client, _ context.Context, _ map[string]interface{}) error { + }).ApplyMethod(reflect.TypeOf(testClient), "UpdateQuota", + func(_ *client.RestClient, _ context.Context, _ map[string]interface{}) error { return errors.New("fail") }) + defer p.Reset() + nas := NewNAS(testClient) err := nas.Expand(context.TODO(), "123", 3221225472) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestCreateConvergedQoS(t *testing.T) { - convey.Convey("Empty", t, func() { + t.Run("Empty", func(t *testing.T) { nas := NewNAS(testClient) param := map[string]interface{}{} taskResult := map[string]interface{}{} _, err := nas.createConvergedQoS(ctx, param, taskResult) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) - convey.Convey("No fsName", t, func() { + t.Run("No fsName", func(t *testing.T) { nas := NewNAS(testClient) param := map[string]interface{}{ "qos": map[string]int{"maxIOPS": 999, "maxMBPS": 999}, } taskResult := map[string]interface{}{} _, err := nas.createConvergedQoS(ctx, param, taskResult) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } func TestPreProcessConvergedQoS(t *testing.T) { - convey.Convey("Empty", t, func() { + t.Run("Empty", func(t *testing.T) { nas := NewNAS(testClient) param := map[string]interface{}{} err := nas.preProcessConvergedQoS(ctx, param) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) - convey.Convey("not json", t, func() { + t.Run("not json", func(t *testing.T) { nas := NewNAS(testClient) param := map[string]interface{}{ "qos": "not json", } err := nas.preProcessConvergedQoS(ctx, param) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) - convey.Convey("normal", t, func() { + t.Run("normal", func(t *testing.T) { nas := NewNAS(testClient) param := map[string]interface{}{ "qos": "{\"maxMBPS\":999,\"maxIOPS\":999}", } err := nas.preProcessConvergedQoS(ctx, param) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) } func TestDeleteConvergedQoSByFsName(t *testing.T) { - convey.Convey("GetQoSPolicyIdByFsName failed", t, func() { - m := gomonkey.ApplyMethod(reflect.TypeOf(&client.Client{}), + t.Run("GetQoSPolicyIdByFsName failed", func(t *testing.T) { + m := gomonkey.ApplyMethod(reflect.TypeOf(&client.RestClient{}), "GetQoSPolicyIdByFsName", - func(_ *client.Client, _ context.Context, _ string) (int, error) { return 0, errors.New("mock-error") }, + func(_ *client.RestClient, _ context.Context, _ string) (int, error) { + return 0, errors.New("mock-error") + }, ) defer m.Reset() nas := NewNAS(testClient) err := nas.deleteConvergedQoSByFsName(ctx, "mock-fs-name") - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) - convey.Convey("GetQoSPolicyIdByFsName empty", t, func() { - m := gomonkey.ApplyMethod(reflect.TypeOf(&client.Client{}), + t.Run("GetQoSPolicyIdByFsName empty", func(t *testing.T) { + m := gomonkey.ApplyMethod(reflect.TypeOf(&client.RestClient{}), "GetQoSPolicyIdByFsName", - func(_ *client.Client, _ context.Context, _ string) (int, error) { return types.NoQoSPolicyId, nil }, + func(_ *client.RestClient, _ context.Context, _ string) (int, error) { return types.NoQoSPolicyId, nil }, ) defer m.Reset() nas := NewNAS(testClient) err := nas.deleteConvergedQoSByFsName(ctx, "mock-fs-name") - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) } diff --git a/storage/fusionstorage/volume/san.go b/storage/fusionstorage/volume/san.go index 28c8f629..1dc8d373 100644 --- a/storage/fusionstorage/volume/san.go +++ b/storage/fusionstorage/volume/san.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,8 @@ import ( "huawei-csi-driver/storage/fusionstorage/client" "huawei-csi-driver/storage/fusionstorage/smartx" "huawei-csi-driver/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" ) const ( @@ -36,15 +36,17 @@ const ( // ISCSITYPE defines iscsi type ISCSITYPE = 1 + + snapshotRandBase = 10000000000 ) // SAN provides san storage client type SAN struct { - cli *client.Client + cli *client.RestClient } // NewSAN inits a new san client -func NewSAN(cli *client.Client) *SAN { +func NewSAN(cli *client.RestClient) *SAN { return &SAN{ cli: cli, } @@ -105,7 +107,7 @@ func (p *SAN) Create(ctx context.Context, params map[string]interface{}) (utils. return nil, err } - taskflow := taskflow.NewTaskFlow(ctx, "Create-FusionStorage-LUN-Volume") + taskflow := flow.NewTaskFlow(ctx, "Create-FusionStorage-LUN-Volume") taskflow.AddTask("Create-LUN", p.createLun, p.revertLun) taskflow.AddTask("Create-QoS", p.createQoS, nil) @@ -189,7 +191,7 @@ func (p *SAN) clone(ctx context.Context, params map[string]interface{}) error { return errors.New(msg) } - snapshotName := fmt.Sprintf("k8s_vol_%s_snap_%d", cloneFrom, utils.RandomInt(10000000000)) + snapshotName := fmt.Sprintf("k8s_vol_%s_snap_%d", cloneFrom, utils.RandomInt(snapshotRandBase)) err = p.cli.CreateSnapshot(ctx, snapshotName, cloneFrom) if err != nil { @@ -364,7 +366,7 @@ func (p *SAN) Expand(ctx context.Context, name string, newSize int64) (bool, err return false, errors.New(msg) } - expandTask := taskflow.NewTaskFlow(ctx, "Expand-LUN-Volume") + expandTask := flow.NewTaskFlow(ctx, "Expand-LUN-Volume") expandTask.AddTask("Expand-PreCheck-Capacity", p.preExpandCheckCapacity, nil) expandTask.AddTask("Expand-Local-Lun", p.expandLocalLun, nil) @@ -449,7 +451,7 @@ func (p *SAN) CreateSnapshot(ctx context.Context, } } - taskflow := taskflow.NewTaskFlow(ctx, "Create-LUN-Snapshot") + taskflow := flow.NewTaskFlow(ctx, "Create-LUN-Snapshot") taskflow.AddTask("Create-Snapshot", p.createSnapshot, nil) _, err = taskflow.Run(map[string]interface{}{ diff --git a/storage/oceanstor/attacher/attacher.go b/storage/oceanstor/attacher/attacher.go index 05a1dafc..3e9d91a1 100644 --- a/storage/oceanstor/attacher/attacher.go +++ b/storage/oceanstor/attacher/attacher.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,20 +32,20 @@ import ( ) const ( - hostGroupType = 14 - lunGroupType = 256 + splitIqnLength = 6 + maxHostNameLength = 31 ) -// AttacherPlugin defines interfaces of attach operations -type AttacherPlugin interface { +// VolumeAttacherPlugin defines interfaces of attach operations +type VolumeAttacherPlugin interface { ControllerAttach(context.Context, string, map[string]interface{}) (map[string]interface{}, error) ControllerDetach(context.Context, string, map[string]interface{}) (string, error) getTargetRoCEPortals(context.Context) ([]string, error) getLunInfo(context.Context, string) (map[string]interface{}, error) } -// Attacher defines attacher to attach volume -type Attacher struct { +// VolumeAttacher defines attacher to attach volume +type VolumeAttacher struct { cli client.BaseClientInterface protocol string invoker string @@ -53,43 +53,48 @@ type Attacher struct { alua map[string]interface{} } +// VolumeAttacherConfig defines the configurations of VolumeAttacher +type VolumeAttacherConfig struct { + Product string + Cli client.BaseClientInterface + Protocol string + Invoker string + Portals []string + Alua map[string]interface{} +} + // NewAttacher init a new attacher -func NewAttacher( - product string, - cli client.BaseClientInterface, - protocol, invoker string, - portals []string, - alua map[string]interface{}) AttacherPlugin { - switch product { +func NewAttacher(config VolumeAttacherConfig) VolumeAttacherPlugin { + switch config.Product { case "DoradoV6": - return newDoradoV6Attacher(cli, protocol, invoker, portals, alua) + return newDoradoV6Attacher(config) default: - return newOceanStorAttacher(cli, protocol, invoker, portals, alua) + return newOceanStorAttacher(config) } } -func (p *Attacher) getHostName(postfix string) string { +func (p *VolumeAttacher) getHostName(postfix string) string { host := fmt.Sprintf("k8s_%s", postfix) - if len(host) <= 31 { + if len(host) <= maxHostNameLength { return host } - return host[:31] + return host[:maxHostNameLength] } -func (p *Attacher) getHostGroupName(postfix string) string { +func (p *VolumeAttacher) getHostGroupName(postfix string) string { return fmt.Sprintf("k8s_%s_hostgroup_%s", p.invoker, postfix) } -func (p *Attacher) getLunGroupName(postfix string) string { +func (p *VolumeAttacher) getLunGroupName(postfix string) string { return fmt.Sprintf("k8s_%s_lungroup_%s", p.invoker, postfix) } -func (p *Attacher) getMappingName(postfix string) string { +func (p *VolumeAttacher) getMappingName(postfix string) string { return fmt.Sprintf("k8s_%s_mapping_%s", p.invoker, postfix) } -func (p *Attacher) getHost(ctx context.Context, +func (p *VolumeAttacher) getHost(ctx context.Context, parameters map[string]interface{}, toCreate bool) (map[string]interface{}, error) { var err error @@ -125,7 +130,7 @@ func (p *Attacher) getHost(ctx context.Context, return nil, nil } -func (p *Attacher) createMapping(ctx context.Context, hostID string) (string, error) { +func (p *VolumeAttacher) createMapping(ctx context.Context, hostID string) (string, error) { mappingName := p.getMappingName(hostID) mapping, err := p.cli.GetMappingByName(ctx, mappingName) if err != nil { @@ -143,12 +148,12 @@ func (p *Attacher) createMapping(ctx context.Context, hostID string) (string, er return mapping["ID"].(string), nil } -func (p *Attacher) createHostGroup(ctx context.Context, hostID, mappingID string) error { +func (p *VolumeAttacher) createHostGroup(ctx context.Context, hostID, mappingID string) error { var err error var hostGroup map[string]interface{} var hostGroupID string - hostGroupsByHostID, err := p.cli.QueryAssociateHostGroup(ctx, 21, hostID) + hostGroupsByHostID, err := p.cli.QueryAssociateHostGroup(ctx, client.AssociateObjTypeHost, hostID) if err != nil { log.AddContext(ctx).Errorf("Query associated hostgroups of host %s error: %v", hostID, err) @@ -201,8 +206,8 @@ func (p *Attacher) createHostGroup(ctx context.Context, hostID, mappingID string return p.addToHostGroupMapping(ctx, hostGroupName, hostGroupID, mappingID) } -func (p *Attacher) addToHostGroupMapping(ctx context.Context, groupName, groupID, mappingID string) error { - hostGroupsByMappingID, err := p.cli.QueryAssociateHostGroup(ctx, 245, mappingID) +func (p *VolumeAttacher) addToHostGroupMapping(ctx context.Context, groupName, groupID, mappingID string) error { + hostGroupsByMappingID, err := p.cli.QueryAssociateHostGroup(ctx, client.AssociateObjTypeMapping, mappingID) if err != nil { log.AddContext(ctx).Errorf("Query associated host groups of mapping %s error: %v", mappingID, err) return err @@ -218,7 +223,7 @@ func (p *Attacher) addToHostGroupMapping(ctx context.Context, groupName, groupID } } - err = p.cli.AddGroupToMapping(ctx, hostGroupType, groupID, mappingID) + err = p.cli.AddGroupToMapping(ctx, client.AssociateObjTypeHostGroup, groupID, mappingID) if err != nil { log.AddContext(ctx).Errorf("Add host group %s to mapping %s error: %v", groupID, mappingID, err) @@ -228,11 +233,11 @@ func (p *Attacher) addToHostGroupMapping(ctx context.Context, groupName, groupID return nil } -func (p *Attacher) createLunGroup(ctx context.Context, lunID, hostID, mappingID string) error { +func (p *VolumeAttacher) createLunGroup(ctx context.Context, lunID, hostID, mappingID string) error { var err error var lunGroup map[string]interface{} - lunGroupsByLunID, err := p.cli.QueryAssociateLunGroup(ctx, 11, lunID) + lunGroupsByLunID, err := p.cli.QueryAssociateLunGroup(ctx, client.AssociateObjTypeLUN, lunID) if err != nil { log.AddContext(ctx).Errorf("Query associated lun groups of lun %s error: %v", lunID, err) return err @@ -280,8 +285,8 @@ func (p *Attacher) createLunGroup(ctx context.Context, lunID, hostID, mappingID return p.addToLUNGroupMapping(ctx, lunGroupName, lunGroupID, mappingID) } -func (p *Attacher) addToLUNGroupMapping(ctx context.Context, groupName, groupID, mappingID string) error { - lunGroupsByMappingID, err := p.cli.QueryAssociateLunGroup(ctx, 245, mappingID) +func (p *VolumeAttacher) addToLUNGroupMapping(ctx context.Context, groupName, groupID, mappingID string) error { + lunGroupsByMappingID, err := p.cli.QueryAssociateLunGroup(ctx, client.AssociateObjTypeMapping, mappingID) if err != nil { log.AddContext(ctx).Errorf("Query associated lun groups of mapping %s error: %v", mappingID, err) return err @@ -297,7 +302,7 @@ func (p *Attacher) addToLUNGroupMapping(ctx context.Context, groupName, groupID, } } - err = p.cli.AddGroupToMapping(ctx, lunGroupType, groupID, mappingID) + err = p.cli.AddGroupToMapping(ctx, client.AssociateObjTypeLUNGroup, groupID, mappingID) if err != nil { log.AddContext(ctx).Errorf("Add lun group %s to mapping %s error: %v", groupID, mappingID, err) @@ -307,7 +312,7 @@ func (p *Attacher) addToLUNGroupMapping(ctx context.Context, groupName, groupID, return nil } -func (p *Attacher) needUpdateInitiatorAlua(initiator map[string]interface{}) bool { +func (p *VolumeAttacher) needUpdateInitiatorAlua(initiator map[string]interface{}) bool { if p.alua == nil { return false } @@ -341,7 +346,7 @@ func (p *Attacher) needUpdateInitiatorAlua(initiator map[string]interface{}) boo return false } -func (p *Attacher) getISCSIProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]interface{}) ( +func (p *VolumeAttacher) getISCSIProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]any) ( map[string]interface{}, error) { tgtPortals, tgtIQNs, err := p.getTargetISCSIProperties(ctx) if err != nil { @@ -362,7 +367,7 @@ func (p *Attacher) getISCSIProperties(ctx context.Context, wwn, hostLunId string }, nil } -func (p *Attacher) getFCProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]interface{}) ( +func (p *VolumeAttacher) getFCProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]any) ( map[string]interface{}, error) { tgtWWNs, err := p.getTargetFCProperties(ctx, parameters) if err != nil { @@ -382,7 +387,7 @@ func (p *Attacher) getFCProperties(ctx context.Context, wwn, hostLunId string, p }, nil } -func (p *Attacher) getFCNVMeProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]interface{}) ( +func (p *VolumeAttacher) getFCNVMeProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]any) ( map[string]interface{}, error) { portWWNList, err := p.getTargetFCNVMeProperties(ctx, parameters) if err != nil { @@ -395,7 +400,7 @@ func (p *Attacher) getFCNVMeProperties(ctx context.Context, wwn, hostLunId strin }, nil } -func (p *Attacher) getRoCEProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]interface{}) ( +func (p *VolumeAttacher) getRoCEProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]any) ( map[string]interface{}, error) { tgtPortals, err := p.getTargetRoCEPortals(ctx) if err != nil { @@ -408,7 +413,7 @@ func (p *Attacher) getRoCEProperties(ctx context.Context, wwn, hostLunId string, }, nil } -func (p *Attacher) getMappingProperties(ctx context.Context, +func (p *VolumeAttacher) getMappingProperties(ctx context.Context, wwn, hostLunId string, parameters map[string]interface{}) (map[string]interface{}, error) { if p.protocol == "iscsi" { return p.getISCSIProperties(ctx, wwn, hostLunId, parameters) @@ -423,7 +428,7 @@ func (p *Attacher) getMappingProperties(ctx context.Context, return nil, utils.Errorf(ctx, "UnSupport protocol %s", p.protocol) } -func (p *Attacher) getTargetISCSIProperties(ctx context.Context) ([]string, []string, error) { +func (p *VolumeAttacher) getTargetISCSIProperties(ctx context.Context) ([]string, []string, error) { ports, err := p.cli.GetIscsiTgtPort(ctx) if err != nil { log.AddContext(ctx).Errorf("Get iSCSI tgt port error: %v", err) @@ -451,12 +456,12 @@ func (p *Attacher) getTargetISCSIProperties(ctx context.Context) ([]string, []st portIqn := strings.Split(strings.Split(portID, ",")[0], "+")[1] splitIqn := strings.Split(portIqn, ":") - if len(splitIqn) < 6 { + if len(splitIqn) < splitIqnLength { continue } - validIPs[splitIqn[5]] = true - validIQNs[splitIqn[5]] = portIqn + validIPs[splitIqn[splitIqnLength-1]] = true + validIQNs[splitIqn[splitIqnLength-1]] = portIqn } var tgtPortals []string @@ -482,7 +487,7 @@ func (p *Attacher) getTargetISCSIProperties(ctx context.Context) ([]string, []st return tgtPortals, tgtIQNs, nil } -func (p *Attacher) getTargetRoCEPortals(ctx context.Context) ([]string, error) { +func (p *VolumeAttacher) getTargetRoCEPortals(ctx context.Context) ([]string, error) { var availablePortals []string for _, portal := range p.portals { ip := net.ParseIP(portal).String() @@ -521,8 +526,8 @@ func (p *Attacher) getTargetRoCEPortals(ctx context.Context) ([]string, error) { return availablePortals, nil } -func (p *Attacher) getTargetFCNVMeProperties(ctx context.Context, parameters map[string]interface{}) ([]nvme.PortWWNPair, error) { - +func (p *VolumeAttacher) getTargetFCNVMeProperties(ctx context.Context, + parameters map[string]interface{}) ([]nvme.PortWWNPair, error) { fcInitiators, err := GetMultipleInitiators(ctx, FC, parameters) if err != nil { log.AddContext(ctx).Errorf("Get fc initiator error:%v", err) @@ -545,7 +550,7 @@ func (p *Attacher) getTargetFCNVMeProperties(ctx context.Context, parameters map return ret, nil } -func (p *Attacher) getTargetFCProperties(ctx context.Context, parameters map[string]interface{}) ([]string, error) { +func (p *VolumeAttacher) getTargetFCProperties(ctx context.Context, parameters map[string]any) ([]string, error) { fcInitiators, err := GetMultipleInitiators(ctx, FC, parameters) if err != nil { log.AddContext(ctx).Errorf("Get fc initiator error: %v", err) @@ -582,7 +587,8 @@ func (p *Attacher) getTargetFCProperties(ctx context.Context, parameters map[str return tgtWWNs, nil } -func (p *Attacher) attachISCSI(ctx context.Context, hostID string, parameters map[string]interface{}) (map[string]interface{}, error) { +func (p *VolumeAttacher) attachISCSI(ctx context.Context, + hostID string, parameters map[string]interface{}) (map[string]interface{}, error) { name, err := GetSingleInitiator(ctx, ISCSI, parameters) if err != nil { log.AddContext(ctx).Errorf("Get ISCSI initiator name error: %v", err) @@ -626,7 +632,8 @@ func (p *Attacher) attachISCSI(ctx context.Context, hostID string, parameters ma return initiator, nil } -func (p *Attacher) attachFC(ctx context.Context, hostID string, parameters map[string]interface{}) ([]map[string]interface{}, error) { +func (p *VolumeAttacher) attachFC(ctx context.Context, + hostID string, parameters map[string]interface{}) ([]map[string]interface{}, error) { fcInitiators, err := GetMultipleInitiators(ctx, FC, parameters) if err != nil { log.AddContext(ctx).Errorf("Get fc initiator error: %v", err) @@ -684,7 +691,8 @@ func (p *Attacher) attachFC(ctx context.Context, hostID string, parameters map[s return hostInitiators, nil } -func (p *Attacher) attachRoCE(ctx context.Context, hostID string, parameters map[string]interface{}) (map[string]interface{}, error) { +func (p *VolumeAttacher) attachRoCE(ctx context.Context, + hostID string, parameters map[string]interface{}) (map[string]interface{}, error) { name, err := GetSingleInitiator(ctx, ROCE, parameters) if err != nil { log.AddContext(ctx).Errorf("Get RoCE initiator name error: %v", err) @@ -728,7 +736,7 @@ func (p *Attacher) attachRoCE(ctx context.Context, hostID string, parameters map return initiator, nil } -func (p *Attacher) doMapping(ctx context.Context, hostID, lunName string) (string, string, error) { +func (p *VolumeAttacher) doMapping(ctx context.Context, hostID, lunName string) (string, string, error) { lun, err := p.cli.GetLunByName(ctx, lunName) if err != nil { log.AddContext(ctx).Errorf("Get lun %s error: %v", lunName, err) @@ -775,7 +783,7 @@ func (p *Attacher) doMapping(ctx context.Context, hostID, lunName string) (strin return lunUniqueId, hostLunId, nil } -func (p *Attacher) doUnmapping(ctx context.Context, hostID, lunName string) (string, error) { +func (p *VolumeAttacher) doUnmapping(ctx context.Context, hostID, lunName string) (string, error) { lun, err := p.cli.GetLunByName(ctx, lunName) if err != nil { log.AddContext(ctx).Errorf("Get lun %s info error: %v", lunName, err) @@ -789,7 +797,7 @@ func (p *Attacher) doUnmapping(ctx context.Context, hostID, lunName string) (str if !ok { return "", pkgUtils.Errorf(ctx, "convert lunID to string failed, data: %v", lun["ID"]) } - lunGroupsByLunID, err := p.cli.QueryAssociateLunGroup(ctx, 11, lunID) + lunGroupsByLunID, err := p.cli.QueryAssociateLunGroup(ctx, client.AssociateObjTypeLUN, lunID) if err != nil { log.AddContext(ctx).Errorf("Query associated lungroups of lun %s error: %v", lunID, err) return "", err @@ -824,7 +832,7 @@ func (p *Attacher) doUnmapping(ctx context.Context, hostID, lunName string) (str } // ControllerDetach detaches volume and unmaps lun from host -func (p *Attacher) ControllerDetach(ctx context.Context, +func (p *VolumeAttacher) ControllerDetach(ctx context.Context, lunName string, parameters map[string]interface{}) (string, error) { host, err := p.getHost(ctx, parameters, false) @@ -850,7 +858,7 @@ func (p *Attacher) ControllerDetach(ctx context.Context, return wwn, nil } -func (p *Attacher) getLunInfo(ctx context.Context, lunName string) (map[string]interface{}, error) { +func (p *VolumeAttacher) getLunInfo(ctx context.Context, lunName string) (map[string]interface{}, error) { lun, err := p.cli.GetLunByName(ctx, lunName) if err != nil { log.AddContext(ctx).Errorf("Get lun %s info error: %v", lunName, err) diff --git a/storage/oceanstor/attacher/attacher_utils.go b/storage/oceanstor/attacher/attacher_utils.go index e3b73175..687bc41e 100644 --- a/storage/oceanstor/attacher/attacher_utils.go +++ b/storage/oceanstor/attacher/attacher_utils.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,8 @@ const ( ) // GetMultipleInitiators use this method when the initiator is an array e.g. fc -func GetMultipleInitiators(ctx context.Context, protocol InitiatorType, parameters map[string]interface{}) ([]string, error) { +func GetMultipleInitiators(ctx context.Context, + protocol InitiatorType, parameters map[string]interface{}) ([]string, error) { initiatorData, err := getInitiatorByProtocol(ctx, protocol, parameters) if err != nil { return nil, err @@ -56,7 +57,8 @@ func GetMultipleInitiators(ctx context.Context, protocol InitiatorType, paramete } // GetSingleInitiator use this method when the initiator is single e.g. iscsi -func GetSingleInitiator(ctx context.Context, protocol InitiatorType, parameters map[string]interface{}) (string, error) { +func GetSingleInitiator(ctx context.Context, + protocol InitiatorType, parameters map[string]interface{}) (string, error) { initiatorData, err := getInitiatorByProtocol(ctx, protocol, parameters) if err != nil { return "", err @@ -69,7 +71,8 @@ func GetSingleInitiator(ctx context.Context, protocol InitiatorType, parameters return "", utils.Errorf(ctx, "convert %v initiator to string error:%v", protocol, initiatorData) } -func getInitiatorByProtocol(ctx context.Context, protocol InitiatorType, parameters map[string]interface{}) (interface{}, error) { +func getInitiatorByProtocol(ctx context.Context, + protocol InitiatorType, parameters map[string]interface{}) (interface{}, error) { hostName, ok := parameters["HostName"].(string) if !ok { return nil, utils.Errorf(ctx, "Get node host name error,parameters:%v ", parameters) diff --git a/storage/oceanstor/attacher/dorado_v6_attacher.go b/storage/oceanstor/attacher/dorado_v6_attacher.go index f6457946..cd0bd835 100644 --- a/storage/oceanstor/attacher/dorado_v6_attacher.go +++ b/storage/oceanstor/attacher/dorado_v6_attacher.go @@ -20,14 +20,13 @@ import ( "context" "errors" - "huawei-csi-driver/storage/oceanstor/client" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) -// DoradoV6Attacher implements interface AttacherPlugin +// DoradoV6Attacher implements interface VolumeAttacherPlugin type DoradoV6Attacher struct { - Attacher + VolumeAttacher } const ( @@ -35,18 +34,14 @@ const ( AccessModeBalanced = "0" ) -func newDoradoV6Attacher( - cli client.BaseClientInterface, - protocol, invoker string, - portals []string, - alua map[string]interface{}) AttacherPlugin { +func newDoradoV6Attacher(config VolumeAttacherConfig) VolumeAttacherPlugin { return &DoradoV6Attacher{ - Attacher: Attacher{ - cli: cli, - protocol: protocol, - invoker: invoker, - portals: portals, - alua: alua, + VolumeAttacher: VolumeAttacher{ + cli: config.Cli, + protocol: config.Protocol, + invoker: config.Invoker, + portals: config.Portals, + alua: config.Alua, }, } } @@ -96,11 +91,11 @@ func (p *DoradoV6Attacher) ControllerAttach(ctx context.Context, } if p.protocol == "iscsi" { - _, err = p.Attacher.attachISCSI(ctx, hostID, parameters) + _, err = p.VolumeAttacher.attachISCSI(ctx, hostID, parameters) } else if p.protocol == "fc" || p.protocol == "fc-nvme" { - _, err = p.Attacher.attachFC(ctx, hostID, parameters) + _, err = p.VolumeAttacher.attachFC(ctx, hostID, parameters) } else if p.protocol == "roce" { - _, err = p.Attacher.attachRoCE(ctx, hostID, parameters) + _, err = p.VolumeAttacher.attachRoCE(ctx, hostID, parameters) } if err != nil { diff --git a/storage/oceanstor/attacher/metroAttacher.go b/storage/oceanstor/attacher/metroAttacher.go index ce15165c..a5c61e8c 100644 --- a/storage/oceanstor/attacher/metroAttacher.go +++ b/storage/oceanstor/attacher/metroAttacher.go @@ -23,15 +23,15 @@ import ( "huawei-csi-driver/utils/log" ) -// MetroAttacher implements interface AttacherPlugin +// MetroAttacher implements interface VolumeAttacherPlugin type MetroAttacher struct { - localAttacher AttacherPlugin - remoteAttacher AttacherPlugin + localAttacher VolumeAttacherPlugin + remoteAttacher VolumeAttacherPlugin protocol string } // NewMetroAttacher inits a new metro attacher -func NewMetroAttacher(localAttacher, remoteAttacher AttacherPlugin, protocol string) *MetroAttacher { +func NewMetroAttacher(localAttacher, remoteAttacher VolumeAttacherPlugin, protocol string) *MetroAttacher { return &MetroAttacher{ localAttacher: localAttacher, remoteAttacher: remoteAttacher, diff --git a/storage/oceanstor/attacher/oceanstor_attacher.go b/storage/oceanstor/attacher/oceanstor_attacher.go index 5a502775..6d3b1a4c 100644 --- a/storage/oceanstor/attacher/oceanstor_attacher.go +++ b/storage/oceanstor/attacher/oceanstor_attacher.go @@ -20,14 +20,13 @@ import ( "context" "errors" - "huawei-csi-driver/storage/oceanstor/client" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) -// OceanStorAttacher implements interface AttacherPlugin +// OceanStorAttacher implements interface VolumeAttacherPlugin type OceanStorAttacher struct { - Attacher + VolumeAttacher } const ( @@ -35,19 +34,14 @@ const ( MultiPathTypeDefault = "0" ) -func newOceanStorAttacher( - cli client.BaseClientInterface, - protocol, - invoker string, - portals []string, - alua map[string]interface{}) AttacherPlugin { +func newOceanStorAttacher(config VolumeAttacherConfig) VolumeAttacherPlugin { return &OceanStorAttacher{ - Attacher: Attacher{ - cli: cli, - protocol: protocol, - invoker: invoker, - portals: portals, - alua: alua, + VolumeAttacher: VolumeAttacher{ + cli: config.Cli, + protocol: config.Protocol, + invoker: config.Invoker, + portals: config.Portals, + alua: config.Alua, }, } } @@ -85,7 +79,7 @@ func (p *OceanStorAttacher) needUpdateInitiatorAlua(initiator map[string]interfa func (p *OceanStorAttacher) attachISCSI(ctx context.Context, hostID, hostName string, parameters map[string]interface{}) error { - iscsiInitiator, err := p.Attacher.attachISCSI(ctx, hostID, parameters) + iscsiInitiator, err := p.VolumeAttacher.attachISCSI(ctx, hostID, parameters) if err != nil { return err } @@ -100,7 +94,7 @@ func (p *OceanStorAttacher) attachISCSI(ctx context.Context, hostID, hostName st func (p *OceanStorAttacher) attachFC(ctx context.Context, hostID, hostName string, parameters map[string]interface{}) error { - fcInitiators, err := p.Attacher.attachFC(ctx, hostID, parameters) + fcInitiators, err := p.VolumeAttacher.attachFC(ctx, hostID, parameters) if err != nil { return err } @@ -123,7 +117,7 @@ func (p *OceanStorAttacher) attachFC(ctx context.Context, hostID, hostName strin } func (p *OceanStorAttacher) attachRoCE(ctx context.Context, hostID string, parameters map[string]interface{}) error { - _, err := p.Attacher.attachRoCE(ctx, hostID, parameters) + _, err := p.VolumeAttacher.attachRoCE(ctx, hostID, parameters) return err } diff --git a/storage/oceanstor/client/client.go b/storage/oceanstor/client/client.go index dc16322e..2514bb32 100644 --- a/storage/oceanstor/client/client.go +++ b/storage/oceanstor/client/client.go @@ -28,6 +28,7 @@ import ( "net/http" "net/http/cookiejar" "regexp" + "slices" "strconv" "sync" "sync/atomic" @@ -55,11 +56,6 @@ const ( // QueryCountPerBatch defines query count for each circle of batch operation QueryCountPerBatch int = 100 - description string = "Created from huawei-csi for Kubernetes" - - defaultVStore string = "System_vStore" - defaultVStoreID string = "0" - // IPLockErrorCode defines error code of ip lock IPLockErrorCode = 1077949071 @@ -76,6 +72,13 @@ const ( UrlNotFound = "404_NotFound" ) +const ( + description string = "Created from huawei-csi for Kubernetes" + defaultVStore string = "System_vStore" + defaultVStoreID string = "0" + defaultHttpTimeout = 60 * time.Second +) + var ( // WrongPasswordErrorCodes user or password is incorrect WrongPasswordErrorCodes = []int64{1077987870, 1077949081, 1077949061} @@ -104,7 +107,6 @@ type BaseClientInterface interface { VStore DTree OceanStorQuota - Container LIF Call(ctx context.Context, method string, url string, data map[string]interface{}) (Response, error) @@ -159,8 +161,8 @@ var ( }, } - // ClientSemaphore provides semaphore of client - ClientSemaphore *utils.Semaphore + // RequestSemaphore provides semaphore of client + RequestSemaphore *utils.Semaphore ) func isFilterLog(method, url string) bool { @@ -215,7 +217,7 @@ func newHTTPClientByBackendID(ctx context.Context, backendID string) (HTTP, erro var defaultUseCert bool client := &http.Client{ Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !defaultUseCert}}, - Timeout: 60 * time.Second, + Timeout: defaultHttpTimeout, } jar, err := cookiejar.New(nil) @@ -258,7 +260,7 @@ func newHTTPClientByCertMeta(ctx context.Context, useCert bool, certMeta string) TLSClientConfig: &tls.Config{InsecureSkipVerify: !useCert, RootCAs: certPool}, }, Jar: jar, - Timeout: 60 * time.Second, + Timeout: defaultHttpTimeout, }, nil } @@ -334,7 +336,7 @@ func NewClient(ctx context.Context, param *NewClientConfig) (*BaseClient, error) } log.AddContext(ctx).Infof("Init parallel count is %d", parallelCount) - ClientSemaphore = utils.NewSemaphore(parallelCount) + RequestSemaphore = utils.NewSemaphore(parallelCount) httpClient, err := newHTTPClientByCertMeta(ctx, param.UseCert, param.CertSecretMeta) if err != nil { @@ -446,7 +448,7 @@ func (cli *BaseClient) GetRequest(ctx context.Context, if data != nil { reqBytes, err := json.Marshal(data) if err != nil { - log.AddContext(ctx).Errorf("json.Marshal data %v error: %v", data, err) + log.AddContext(ctx).Errorf("json.Marshal data %v error: %v", maskRequestData(data), err) return req, err } reqBody = bytes.NewReader(reqBytes) @@ -501,8 +503,12 @@ func (cli *BaseClient) BaseCall(ctx context.Context, log.FilteredLog(ctx, isFilterLog(method, url), utils.IsDebugLog(method, url, debugLog, debugLogRegex), fmt.Sprintf("Request method: %s, Url: %s, body: %v", method, req.URL, data)) - ClientSemaphore.Acquire() - defer ClientSemaphore.Release() + if RequestSemaphore == nil { + return r, errors.New("request semaphore is nil") + } + + RequestSemaphore.Acquire() + defer RequestSemaphore.Release() resp, err := cli.Client.Do(req) if err != nil { @@ -560,9 +566,9 @@ func (cli *BaseClient) SafeBaseCall(ctx context.Context, log.FilteredLog(ctx, isFilterLog(method, url), utils.IsDebugLog(method, url, debugLog, debugLogRegex), fmt.Sprintf("Request method: %s, Url: %s, body: %v", method, req.URL, data)) - if ClientSemaphore != nil { - ClientSemaphore.Acquire() - defer ClientSemaphore.Release() + if RequestSemaphore != nil { + RequestSemaphore.Acquire() + defer RequestSemaphore.Release() } return cli.safeDoCall(ctx, method, url, req) @@ -901,7 +907,7 @@ func (cli *BaseClient) getCountFromResponse(ctx context.Context, data interface{ if !ok { return 0, utils.Errorf(ctx, "The COUNT is not in respData %v", respData) } - count, err := strconv.ParseInt(countStr, 10, 64) + count, err := strconv.ParseInt(countStr, constants.DefaultIntBase, constants.DefaultIntBitSize) if err != nil { return 0, err } @@ -933,7 +939,7 @@ func (cli *BaseClient) getSystemUTCTime(ctx context.Context) (int64, error) { return 0, utils.Errorf(ctx, "The CMO_SYS_UTC_TIME is not in respData %v", respData) } - time, err := strconv.ParseInt(utcTime, 10, 64) + time, err := strconv.ParseInt(utcTime, constants.DefaultIntBase, constants.DefaultIntBitSize) if err != nil { return 0, err } @@ -1115,3 +1121,18 @@ func (cli *BaseClient) setLifInfo(ctx context.Context) { func (cli *BaseClient) systemInfoRefreshing() bool { return atomic.LoadUint32(&cli.SystemInfoRefreshing) == 1 } + +func maskRequestData(data map[string]any) map[string]any { + sensitiveKey := []string{"user", "password", "iqn", "tgt", "tgtname", "initiatorname"} + + maskedData := make(map[string]any) + for k, v := range data { + if slices.Contains(sensitiveKey, k) { + maskedData[k] = "***" + } else { + maskedData[k] = v + } + } + + return maskedData +} diff --git a/storage/oceanstor/client/client_dtree.go b/storage/oceanstor/client/client_dtree.go index 6a1ed629..b82a54c9 100644 --- a/storage/oceanstor/client/client_dtree.go +++ b/storage/oceanstor/client/client_dtree.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,8 @@ func (cli *BaseClient) CreateDTree(ctx context.Context, params map[string]interf } // GetDTreeByName use for get dTree information -func (cli *BaseClient) GetDTreeByName(ctx context.Context, parentID, parentName, vStoreID, name string) (map[string]interface{}, error) { +func (cli *BaseClient) GetDTreeByName(ctx context.Context, + parentID, parentName, vStoreID, name string) (map[string]interface{}, error) { url := fmt.Sprintf("/QUOTATREE?PARENTNAME=%s&NAME=%s&vstoreId=%s", parentName, name, vStoreID) resp, err := cli.Get(ctx, url, nil) diff --git a/storage/oceanstor/client/client_filesystem.go b/storage/oceanstor/client/client_filesystem.go index e3481f0e..4c2d0786 100644 --- a/storage/oceanstor/client/client_filesystem.go +++ b/storage/oceanstor/client/client_filesystem.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( "strconv" "time" + "huawei-csi-driver/pkg/constants" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" ) @@ -37,6 +38,7 @@ const ( msgTimeOut int64 = 1077949001 exceedFSCapacityUpper int64 = 1073844377 lessFSCapacityLower int64 = 1073844376 + queryNfsSharePerPage int64 = 100 ) const ( @@ -59,7 +61,7 @@ type Filesystem interface { // GetNfsShareAccessCount used for get nfs share access count by id GetNfsShareAccessCount(ctx context.Context, parentID, vStoreID string) (int64, error) // GetNfsShareAccessRange used for get nfs share access - GetNfsShareAccessRange(ctx context.Context, parentID, vStoreID string, startRange, endRange int64) ([]interface{}, error) + GetNfsShareAccessRange(ctx context.Context, parentID, vStoreID string, startRange, endRange int64) ([]any, error) // CreateFileSystem used for create file system CreateFileSystem(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) // UpdateFileSystem used for update file system @@ -223,8 +225,8 @@ func (cli *BaseClient) GetNfsShareAccess(ctx context.Context, } var i int64 - for i = 0; i < count; i += 100 { // Query per page 100 - clients, err := cli.GetNfsShareAccessRange(ctx, parentID, vStoreID, i, i+100) + for i = 0; i < count; i += queryNfsSharePerPage { // Query per page 100 + clients, err := cli.GetNfsShareAccessRange(ctx, parentID, vStoreID, i, i+queryNfsSharePerPage) if err != nil { return nil, err } @@ -273,7 +275,7 @@ func (cli *BaseClient) GetNfsShareAccessCount(ctx context.Context, parentID, vSt if !ok { return 0, errors.New("convert respData[\"COUNT\"] to string failed") } - count := utils.ParseIntWithDefault(countStr, 10, 64, 0) + count := utils.ParseIntWithDefault(countStr, constants.DefaultIntBase, constants.DefaultIntBitSize, 0) return count, nil } diff --git a/storage/oceanstor/client/client_filesystem_test.go b/storage/oceanstor/client/client_filesystem_test.go index 2a1b0267..86062782 100644 --- a/storage/oceanstor/client/client_filesystem_test.go +++ b/storage/oceanstor/client/client_filesystem_test.go @@ -19,28 +19,24 @@ package client import ( "context" "errors" - "reflect" "testing" - "bou.ke/monkey" - "github.com/smartystreets/goconvey/convey" + "github.com/agiledragon/gomonkey/v2" + "github.com/stretchr/testify/require" ) func TestAllowNfsShareAccess(t *testing.T) { - convey.Convey("Normal", t, func() { - guard := monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "Post", - func(_ *BaseClient, _ context.Context, _ string, _ map[string]interface{}) (Response, error) { - return Response{ - Data: map[string]interface{}{ - "ID": "5", - }, - Error: map[string]interface{}{ - "code": float64(0), - "description": "0", - }, - }, nil - }) - defer guard.Unpatch() + t.Run("Normal", func(t *testing.T) { + p := gomonkey.ApplyMethodReturn(testClient, "Post", Response{ + Data: map[string]interface{}{ + "ID": "5", + }, + Error: map[string]interface{}{ + "code": float64(0), + "description": "0", + }, + }, nil) + defer p.Reset() err := testClient.AllowNfsShareAccess(context.TODO(), &AllowNfsShareAccessRequest{ Name: "test", @@ -51,11 +47,11 @@ func TestAllowNfsShareAccess(t *testing.T) { RootSquash: 1, VStoreID: "0", }) - convey.So(err, convey.ShouldBeNil) + require.NoError(t, err) }) - convey.Convey("Error code is not zero", t, func() { - guard := monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "Post", + t.Run("Error code is not zero", func(t *testing.T) { + p := gomonkey.ApplyMethod(testClient, "Post", func(_ *BaseClient, _ context.Context, _ string, _ map[string]interface{}) (Response, error) { return Response{ Data: map[string]interface{}{ @@ -67,7 +63,7 @@ func TestAllowNfsShareAccess(t *testing.T) { }, }, nil }) - defer guard.Unpatch() + defer p.Reset() err := testClient.AllowNfsShareAccess(context.TODO(), &AllowNfsShareAccessRequest{ Name: "test", @@ -78,15 +74,15 @@ func TestAllowNfsShareAccess(t *testing.T) { RootSquash: 1, VStoreID: "0", }) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) - convey.Convey("Post quest return error", t, func() { - guard := monkey.PatchInstanceMethod(reflect.TypeOf(testClient), "Post", + t.Run("Post quest return error", func(t *testing.T) { + p := gomonkey.ApplyMethod(testClient, "Post", func(_ *BaseClient, _ context.Context, _ string, _ map[string]interface{}) (Response, error) { return Response{}, errors.New("mock err") }) - defer guard.Unpatch() + defer p.Reset() err := testClient.AllowNfsShareAccess(context.TODO(), &AllowNfsShareAccessRequest{ Name: "test", @@ -97,6 +93,6 @@ func TestAllowNfsShareAccess(t *testing.T) { RootSquash: 1, VStoreID: "0", }) - convey.So(err, convey.ShouldBeError) + require.Error(t, err) }) } diff --git a/storage/oceanstor/client/client_host.go b/storage/oceanstor/client/client_host.go index 2adcc6b7..4cc96fd4 100644 --- a/storage/oceanstor/client/client_host.go +++ b/storage/oceanstor/client/client_host.go @@ -32,6 +32,19 @@ const ( hostGroupNotExist int64 = 1077937500 ) +const ( + // AssociateObjTypeMapping mapping type + AssociateObjTypeMapping = 245 + // AssociateObjTypeHost host type + AssociateObjTypeHost = 21 + // AssociateObjTypeHostGroup host group type + AssociateObjTypeHostGroup = 14 + // AssociateObjTypeLUN LUN type + AssociateObjTypeLUN = 11 + // AssociateObjTypeLUNGroup LUN group type + AssociateObjTypeLUNGroup = 256 +) + // Host defines interfaces for host operations type Host interface { // QueryAssociateHostGroup used for query associate host group diff --git a/storage/oceanstor/client/client_hypermetro.go b/storage/oceanstor/client/client_hypermetro.go index cc8e6a5f..bea428d6 100644 --- a/storage/oceanstor/client/client_hypermetro.go +++ b/storage/oceanstor/client/client_hypermetro.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,17 @@ const ( hyperMetroNotExist int64 = 1077674242 ) +const ( + // MetroPairSyncSpeedLow is low synchronization rate of the HyperMetro pair. + MetroPairSyncSpeedLow = iota + 1 + // MetroPairSyncSpeedMedium is medium synchronization rate of the HyperMetro pair. + MetroPairSyncSpeedMedium + // MetroPairSyncSpeedHigh is high synchronization rate of the HyperMetro pair. + MetroPairSyncSpeedHigh + // MetroPairSyncSpeedHighest is the highest synchronization rate of the HyperMetro pair. + MetroPairSyncSpeedHighest +) + // HyperMetro defines interfaces for hyper metro operations type HyperMetro interface { // GetHyperMetroDomainByName used for get hyper metro domain by name @@ -176,7 +187,7 @@ func (cli *BaseClient) GetHyperMetroPair(ctx context.Context, pairID string) (ma } // GetHyperMetroPairByLocalObjID used for get hyper metro pair by local object id -func (cli *BaseClient) GetHyperMetroPairByLocalObjID(ctx context.Context, objID string) (map[string]interface{}, error) { +func (cli *BaseClient) GetHyperMetroPairByLocalObjID(ctx context.Context, objID string) (map[string]any, error) { url := fmt.Sprintf("/HyperMetroPair?filter=LOCALOBJID::%s", objID) resp, err := cli.Get(ctx, url, nil) diff --git a/storage/oceanstor/client/client_lun.go b/storage/oceanstor/client/client_lun.go index 6d6b13ab..dab00df0 100644 --- a/storage/oceanstor/client/client_lun.go +++ b/storage/oceanstor/client/client_lun.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import ( "fmt" "strconv" + "huawei-csi-driver/pkg/constants" pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/utils" "huawei-csi-driver/utils/log" @@ -34,6 +35,8 @@ const ( lunAlreadyInGroup int64 = 1077936862 lunNotExist int64 = 1077936859 parameterIncorrect int64 = 50331651 + + maxLunNameLength = 31 ) // Lun defines interfaces for lun operations @@ -130,10 +133,10 @@ func (cli *BaseClient) GetLunByName(ctx context.Context, name string) (map[strin // MakeLunName v3/v5 storage support 1 to 31 characters func (cli *BaseClient) MakeLunName(name string) string { - if len(name) <= 31 { + if len(name) <= maxLunNameLength { return name } - return name[:31] + return name[:maxLunNameLength] } // GetLunByID used for get lun by id @@ -394,7 +397,7 @@ func (cli *BaseClient) GetLunCountOfMapping(ctx context.Context, mappingID strin return 0, pkgUtils.Errorf(ctx, "convert countStr to string failed, data: %v", respData["COUNT"]) } - count := utils.ParseIntWithDefault(countStr, 10, 64, 0) + count := utils.ParseIntWithDefault(countStr, constants.DefaultIntBase, constants.DefaultIntBitSize, 0) return count, nil } @@ -421,7 +424,7 @@ func (cli *BaseClient) GetLunCountOfHost(ctx context.Context, hostID string) (in if !ok { return 0, pkgUtils.Errorf(ctx, "convert countStr to string failed, data: %v", respData["COUNT"]) } - count := utils.ParseIntWithDefault(countStr, 10, 64, 0) + count := utils.ParseIntWithDefault(countStr, constants.DefaultIntBase, constants.DefaultIntBitSize, 0) return count, nil } @@ -460,7 +463,7 @@ func (cli *BaseClient) GetHostLunId(ctx context.Context, hostID, lunID string) ( } hostLunIdFloat, ok := associateData["HostLUNID"].(float64) if ok { - hostLunId = strconv.FormatInt(int64(hostLunIdFloat), 10) + hostLunId = strconv.FormatInt(int64(hostLunIdFloat), constants.DefaultIntBase) break } } diff --git a/storage/oceanstor/client/client_qos.go b/storage/oceanstor/client/client_qos.go index adbc2491..a17f10ee 100644 --- a/storage/oceanstor/client/client_qos.go +++ b/storage/oceanstor/client/client_qos.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ type Qos interface { // DeleteQos used for delete qos DeleteQos(ctx context.Context, qosID, vStoreID string) error // CreateQos used for create qos - CreateQos(ctx context.Context, name, objID, objType, vStoreID string, params map[string]int) (map[string]interface{}, error) + CreateQos(ctx context.Context, args CreateQoSArgs) (map[string]any, error) // UpdateQos used for update qos UpdateQos(ctx context.Context, qosID, vStoreID string, params map[string]interface{}) error // ActivateQos used for active qos @@ -47,9 +47,17 @@ type Qos interface { DeactivateQos(ctx context.Context, qosID, vStoreID string) error } +// CreateQoSArgs is the arguments to create QoS +type CreateQoSArgs struct { + Name string + ObjID string + ObjType string + VStoreID string + Params map[string]int +} + // CreateQos used for create qos -func (cli *BaseClient) CreateQos(ctx context.Context, name, objID, objType, vStoreID string, params map[string]int) ( - map[string]interface{}, error) { +func (cli *BaseClient) CreateQos(ctx context.Context, args CreateQoSArgs) (map[string]any, error) { utcTime, err := cli.getSystemUTCTime(ctx) if err != nil { @@ -63,24 +71,24 @@ func (cli *BaseClient) CreateQos(ctx context.Context, name, objID, objType, vSto } data := map[string]interface{}{ - "NAME": name, + "NAME": args.Name, "SCHEDULEPOLICY": 1, "SCHEDULESTARTTIME": utcZeroTime.Unix(), "STARTTIME": "00:00", "DURATION": 86400, } - if objType == "fs" { - data["FSLIST"] = []string{objID} + if args.ObjType == "fs" { + data["FSLIST"] = []string{args.ObjID} } else { - data["LUNLIST"] = []string{objID} + data["LUNLIST"] = []string{args.ObjID} } - if vStoreID != "" { - data["vstoreId"] = vStoreID + if args.VStoreID != "" { + data["vstoreId"] = args.VStoreID } - for k, v := range params { + for k, v := range args.Params { data[k] = v } @@ -91,8 +99,8 @@ func (cli *BaseClient) CreateQos(ctx context.Context, name, objID, objType, vSto code := int64(resp.Error["code"].(float64)) if code == smartQosAlreadyExist { - log.AddContext(ctx).Warningf("The QoS %s is already exist.", name) - return cli.GetQosByName(ctx, name, vStoreID) + log.AddContext(ctx).Warningf("The QoS %s is already exist.", args.Name) + return cli.GetQosByName(ctx, args.Name, args.VStoreID) } else if code != 0 { return nil, fmt.Errorf("Create qos %v error: %d", data, code) } diff --git a/storage/oceanstor/client/client_quota.go b/storage/oceanstor/client/client_quota.go index 579684e2..1714f901 100644 --- a/storage/oceanstor/client/client_quota.go +++ b/storage/oceanstor/client/client_quota.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,7 +86,8 @@ func (cli *BaseClient) UpdateQuota(ctx context.Context, quotaID string, params m } // GetQuota gets quota info by id -func (cli *BaseClient) GetQuota(ctx context.Context, quotaID, vStoreID string, spaceUnitType uint32) (map[string]interface{}, error) { +func (cli *BaseClient) GetQuota(ctx context.Context, + quotaID, vStoreID string, spaceUnitType uint32) (map[string]interface{}, error) { resp, err := cli.Get(ctx, fmt.Sprintf("/FS_QUOTA/%v", quotaID), map[string]interface{}{ "SPACEUNITTYPE": spaceUnitType, "vstoreId": vStoreID, diff --git a/storage/oceanstor/client/client_test.go b/storage/oceanstor/client/client_test.go index b0362058..1c87baef 100644 --- a/storage/oceanstor/client/client_test.go +++ b/storage/oceanstor/client/client_test.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "errors" + "io" "io/ioutil" "net/http" "reflect" @@ -78,7 +79,7 @@ func TestLogin(t *testing.T) { defer m.Reset() for _, s := range cases { - g := gomonkey.ApplyMethod(reflect.TypeOf(testClient.Client), "Do", func(_ *http.Client, req *http.Request) (*http.Response, error) { + g := gomonkey.ApplyMethod(testClient.Client, "Do", func(_ *http.Client, req *http.Request) (*http.Response, error) { r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) return &http.Response{ StatusCode: 200, @@ -250,23 +251,25 @@ func TestGetLunByName(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.GetLunByName(context.TODO(), "zfy") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.GetLunByName(context.TODO(), "zfy") + assert.Equal(t, s.wantErr, err != nil, "%v", err) + }) } } @@ -346,23 +349,25 @@ func TestGetLunByID(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.GetLunByID(context.TODO(), "0") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.GetLunByID(context.TODO(), "0") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -396,23 +401,25 @@ func TestAddLunToGroup(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + t.Run(s.Name, func(t *testing.T) { + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - err := testClient.AddLunToGroup(context.TODO(), "", "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + err := testClient.AddLunToGroup(context.TODO(), "", "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -441,23 +448,25 @@ func TestRemoveLunFromGroup(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - err := testClient.RemoveLunFromGroup(context.TODO(), "", "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + err := testClient.RemoveLunFromGroup(context.TODO(), "", "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -490,23 +499,25 @@ func TestGetLunGroupByName(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.GetLunGroupByName(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.GetLunGroupByName(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -536,23 +547,25 @@ func TestCreateLunGroup(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.CreateLunGroup(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.CreateLunGroup(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -581,23 +594,25 @@ func TestDeleteLunGroup(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - err := testClient.DeleteLunGroup(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + err := testClient.DeleteLunGroup(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -628,23 +643,25 @@ func TestQueryAssociateLunGroup(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.QueryAssociateLunGroup(context.TODO(), 245, "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.QueryAssociateLunGroup(context.TODO(), 245, "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -673,23 +690,25 @@ func TestDeleteLun(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - err := testClient.DeleteLun(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + err := testClient.DeleteLun(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -737,23 +756,25 @@ func TestGetPoolByName(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.GetPoolByName(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.GetPoolByName(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -801,23 +822,25 @@ func TestGetAllPools(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.GetAllPools(context.TODO()) - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.GetAllPools(context.TODO()) + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -851,23 +874,25 @@ func TestCreateHost(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.CreateHost(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.CreateHost(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -906,23 +931,25 @@ func TestGetHostByName(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.GetHostByName(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.GetHostByName(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -951,23 +978,25 @@ func TestDeleteHost(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - err := testClient.DeleteHost(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + err := testClient.DeleteHost(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -999,23 +1028,25 @@ func TestCreateHostGroup(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).AnyTimes() + t.Run(s.Name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.ResponseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).AnyTimes() - _, err := testClient.CreateHostGroup(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.Name, err) + _, err := testClient.CreateHostGroup(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -1059,23 +1090,25 @@ func TestGetHostGroupByName(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.responseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).Times(1) + t.Run(s.name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.responseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).Times(1) - _, err := testClient.GetHostGroupByName(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.name, err) + _, err := testClient.GetHostGroupByName(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -1114,23 +1147,25 @@ func TestDeleteHostGroup(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.responseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, nil - }).Times(1) + t.Run(s.name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.responseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, nil + }).Times(1) - err := testClient.DeleteHostGroup(context.TODO(), "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.name, err) + err := testClient.DeleteHostGroup(context.TODO(), "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -1343,22 +1378,24 @@ func TestAddHostToGroup(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := NewMockHTTPClient(ctrl) temp := testClient.Client defer func() { testClient.Client = temp }() - testClient.Client = mockClient for _, s := range cases { - mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.responseBody))) - return &http.Response{ - StatusCode: int(successStatus), - Body: r, - }, s.err - }).Times(1) - err := testClient.AddHostToGroup(context.TODO(), "", "") - assert.Equal(t, s.wantErr, err != nil, "%s, err:%v", s.name, err) + t.Run(s.name, func(t *testing.T) { + mockClient := NewMockHTTPClient(ctrl) + testClient.Client = mockClient + mockClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(req *http.Request) (*http.Response, error) { + r := io.NopCloser(bytes.NewReader([]byte(s.responseBody))) + return &http.Response{ + StatusCode: int(successStatus), + Body: r, + }, s.err + }).Times(1) + err := testClient.AddHostToGroup(context.TODO(), "", "") + assert.Equal(t, s.wantErr, err != nil, "err:%v", err) + }) } } @@ -1404,7 +1441,7 @@ func TestRemoveHostFromGroup(t *testing.T) { for _, s := range cases { d := gomonkey.ApplyMethod(reflect.TypeOf(testClient.Client), "Do", func(*http.Client, *http.Request) (*http.Response, error) { - r := ioutil.NopCloser(bytes.NewReader([]byte(s.responseBody))) + r := io.NopCloser(bytes.NewReader([]byte(s.responseBody))) return &http.Response{ StatusCode: 200, Body: r, diff --git a/storage/oceanstor/clientv6/clientv6.go b/storage/oceanstor/clientv6/clientv6.go index f9f2361f..e650857a 100644 --- a/storage/oceanstor/clientv6/clientv6.go +++ b/storage/oceanstor/clientv6/clientv6.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,13 +26,13 @@ import ( "huawei-csi-driver/utils/log" ) -// ClientV6 provides base client of clientv6 -type ClientV6 struct { +// V6Client provides base client of clientv6 +type V6Client struct { client.BaseClient } // NewClientV6 inits a new client of clientv6 -func NewClientV6(ctx context.Context, param *client.NewClientConfig) (*ClientV6, error) { +func NewClientV6(ctx context.Context, param *client.NewClientConfig) (*V6Client, error) { var err error var parallelCount int @@ -48,20 +48,19 @@ func NewClientV6(ctx context.Context, param *client.NewClientConfig) (*ClientV6, } log.AddContext(ctx).Infof("Init parallel count is %d", parallelCount) - client.ClientSemaphore = utils.NewSemaphore(parallelCount) + client.RequestSemaphore = utils.NewSemaphore(parallelCount) cli, err := client.NewClient(ctx, param) if err != nil { return nil, err } - return &ClientV6{ - *cli, - }, nil + return &V6Client{BaseClient: *cli}, nil } // SplitCloneFS used to split clone for dorado or oceantor v6 -func (cli *ClientV6) SplitCloneFS(ctx context.Context, fsID, vStoreId string, splitSpeed int, deleteParentSnapshot bool) error { +func (cli *V6Client) SplitCloneFS(ctx context.Context, + fsID, vStoreId string, splitSpeed int, deleteParentSnapshot bool) error { data := map[string]interface{}{ "ID": fsID, "SPLITSPEED": splitSpeed, @@ -84,6 +83,6 @@ func (cli *ClientV6) SplitCloneFS(ctx context.Context, fsID, vStoreId string, sp } // MakeLunName v6 storage lun name support 1 to 255 characters -func (cli *ClientV6) MakeLunName(name string) string { +func (cli *V6Client) MakeLunName(name string) string { return name } diff --git a/storage/oceanstor/clientv6/clientv6_test.go b/storage/oceanstor/clientv6/clientv6_test.go index 8a6ed8a7..d18961a4 100644 --- a/storage/oceanstor/clientv6/clientv6_test.go +++ b/storage/oceanstor/clientv6/clientv6_test.go @@ -30,7 +30,7 @@ import ( "huawei-csi-driver/utils/log" ) -var testClient *ClientV6 +var testClient *V6Client const ( logName = "clientV6_test.log" diff --git a/storage/oceanstor/smartx/smartx.go b/storage/oceanstor/smartx/smartx.go index 76f340b1..256e23d8 100644 --- a/storage/oceanstor/smartx/smartx.go +++ b/storage/oceanstor/smartx/smartx.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,17 @@ import ( "huawei-csi-driver/utils/log" ) +const ( + kilo = 1000 + minLatency, maxLatency = 500, 1500 + minIops, maxIops = 99, 999999999 + minBandwidth, maxBandwidth = 0, 999999999 + validIOType0 = 0 + validIOType1 = 1 + validIOType2 = 2 + ioType = 2 +) + type qosParameterValidators map[string]func(int) bool type qosParameterList map[string]struct{} @@ -45,19 +56,19 @@ var ( doradoParameterValidators = map[string]func(int) bool{ "IOTYPE": func(value int) bool { - return value == 2 + return value == validIOType2 }, "MAXBANDWIDTH": func(value int) bool { return value > 0 }, "MAXIOPS": func(value int) bool { - return value > 99 + return value > minIops }, } oceanStorV3V5ParameterValidators = map[string]func(int) bool{ "IOTYPE": func(value int) bool { - return value == 0 || value == 1 || value == 2 + return value == validIOType0 || value == validIOType1 || value == validIOType2 }, "MAXBANDWIDTH": func(value int) bool { return value > 0 @@ -78,28 +89,28 @@ var ( doradoV6ParameterValidators = map[string]func(int) bool{ "IOTYPE": func(value int) bool { - return value == 2 + return value == ioType }, "MAXBANDWIDTH": func(value int) bool { - return value > 0 && value <= 999999999 + return value > minBandwidth && value <= maxBandwidth }, "MINBANDWIDTH": func(value int) bool { - return value > 0 && value <= 999999999 + return value > minBandwidth && value <= maxBandwidth }, "MAXIOPS": func(value int) bool { - return value > 99 && value <= 999999999 + return value > minIops && value <= maxIops }, "MINIOPS": func(value int) bool { - return value > 99 && value <= 999999999 + return value > minIops && value <= maxIops }, "LATENCY": func(value int) bool { // User request Latency values in millisecond but during extraction values are converted in microsecond // as required in OceanStor DoradoV6 QoS create interface - return value == 500 || value == 1500 + return value == minLatency || value == maxLatency }, } @@ -194,7 +205,7 @@ func ExtractQoSParameters(ctx context.Context, product string, qosConfig string) if product == constants.OceanStorDoradoV6 && key == "LATENCY" { // convert OceanStoreDoradoV6 Latency from millisecond to microsecond - params[key] = value * 1000 + params[key] = value * kilo continue } @@ -237,25 +248,25 @@ func ValidateQoSParameters(product string, qosParam map[string]float64) (map[str return validatedParameters, nil } -// SmartX provides smartx client -type SmartX struct { +// Client provides smartx client +type Client struct { cli client.BaseClientInterface } // NewSmartX inits a new smartx client -func NewSmartX(cli client.BaseClientInterface) *SmartX { - return &SmartX{ +func NewSmartX(cli client.BaseClientInterface) *Client { + return &Client{ cli: cli, } } -func (p *SmartX) getQosName(objID, objType string) string { +func (p *Client) getQosName(objID, objType string) string { now := time.Now().Format("20060102150405") return fmt.Sprintf("k8s_%s%s_%s", objType, objID, now) } // CreateQos creates qos and return its id -func (p *SmartX) CreateQos(ctx context.Context, +func (p *Client) CreateQos(ctx context.Context, objID, objType, vStoreID string, params map[string]int) (string, error) { var err error @@ -285,7 +296,7 @@ func (p *SmartX) CreateQos(ctx context.Context, } name := p.getQosName(objID, objType) - qos, err := p.cli.CreateQos(ctx, name, objID, objType, vStoreID, params) + qos, err := p.cli.CreateQos(ctx, p.getCreateQosArgs(name, objID, objType, vStoreID, params)) if err != nil { log.AddContext(ctx).Errorf("Create qos %v for obj %s of type %s error: %v", params, objID, objType, err) @@ -314,7 +325,7 @@ func (p *SmartX) CreateQos(ctx context.Context, } // DeleteQos deletes qos by id -func (p *SmartX) DeleteQos(ctx context.Context, qosID, objID, objType, vStoreID string) error { +func (p *Client) DeleteQos(ctx context.Context, qosID, objID, objType, vStoreID string) error { qos, err := p.cli.GetQosByID(ctx, qosID, vStoreID) if err != nil { log.AddContext(ctx).Errorf("Get qos by ID %s error: %v", qosID, err) @@ -377,7 +388,7 @@ func (p *SmartX) DeleteQos(ctx context.Context, qosID, objID, objType, vStoreID } // CreateLunSnapshot creates lun snapshot -func (p *SmartX) CreateLunSnapshot(ctx context.Context, name, srcLunID string) (map[string]interface{}, error) { +func (p *Client) CreateLunSnapshot(ctx context.Context, name, srcLunID string) (map[string]interface{}, error) { snapshot, err := p.cli.CreateLunSnapshot(ctx, name, srcLunID) if err != nil { log.AddContext(ctx).Errorf("Create snapshot %s for lun %s error: %v", name, srcLunID, err) @@ -399,7 +410,7 @@ func (p *SmartX) CreateLunSnapshot(ctx context.Context, name, srcLunID string) ( } // DeleteLunSnapshot deletes lun snapshot by id -func (p *SmartX) DeleteLunSnapshot(ctx context.Context, snapshotID string) error { +func (p *Client) DeleteLunSnapshot(ctx context.Context, snapshotID string) error { err := p.cli.DeactivateLunSnapshot(ctx, snapshotID) if err != nil { log.AddContext(ctx).Errorf("Deactivate snapshot %s error: %v", snapshotID, err) @@ -416,7 +427,7 @@ func (p *SmartX) DeleteLunSnapshot(ctx context.Context, snapshotID string) error } // CreateFSSnapshot creates fs snapshot -func (p *SmartX) CreateFSSnapshot(ctx context.Context, name, srcFSID string) (string, error) { +func (p *Client) CreateFSSnapshot(ctx context.Context, name, srcFSID string) (string, error) { snapshot, err := p.cli.CreateFSSnapshot(ctx, name, srcFSID) if err != nil { log.AddContext(ctx).Errorf("Create snapshot %s for FS %s error: %v", name, srcFSID, err) @@ -431,7 +442,7 @@ func (p *SmartX) CreateFSSnapshot(ctx context.Context, name, srcFSID string) (st } // DeleteFSSnapshot deletes fs snapshot by id -func (p *SmartX) DeleteFSSnapshot(ctx context.Context, snapshotID string) error { +func (p *Client) DeleteFSSnapshot(ctx context.Context, snapshotID string) error { err := p.cli.DeleteFSSnapshot(ctx, snapshotID) if err != nil { @@ -441,3 +452,13 @@ func (p *SmartX) DeleteFSSnapshot(ctx context.Context, snapshotID string) error return nil } + +func (p *Client) getCreateQosArgs(name, objID, objType, vStoreID string, params map[string]int) client.CreateQoSArgs { + return client.CreateQoSArgs{ + Name: name, + ObjID: objID, + ObjType: objType, + VStoreID: vStoreID, + Params: params, + } +} diff --git a/storage/oceanstor/volume/base.go b/storage/oceanstor/volume/base.go index 40d924d0..f380be51 100644 --- a/storage/oceanstor/volume/base.go +++ b/storage/oceanstor/volume/base.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import ( "fmt" "strconv" + "huawei-csi-driver/pkg/constants" pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/storage/oceanstor/client" "huawei-csi-driver/storage/oceanstor/smartx" @@ -42,6 +43,7 @@ func (p *Base) commonPreModify(ctx context.Context, params map[string]interface{ p.getAllocType, p.getQoS, p.getFileMode, + p.getMetroPairSyncSpeed, } for _, analyzer := range analyzers { @@ -61,6 +63,7 @@ func (p *Base) commonPreCreate(ctx context.Context, params map[string]interface{ p.getPoolID, p.getQoS, p.getFileMode, + p.getMetroPairSyncSpeed, } for _, analyzer := range analyzers { @@ -93,16 +96,17 @@ func (p *Base) getCloneSpeed(_ context.Context, params map[string]interface{}) e if v, exist := params["clonespeed"].(string); exist && v != "" { speed, err := strconv.Atoi(v) - if err != nil || speed < 1 || speed > 4 { + if err != nil || speed < constants.CloneSpeedLevel1 || speed > constants.CloneSpeedLevel4 { return fmt.Errorf("error config %s for clonespeed", v) } params["clonespeed"] = speed } else { - params["clonespeed"] = 3 + params["clonespeed"] = constants.CloneSpeedLevel3 } return nil } + func (p *Base) getFileMode(_ context.Context, params map[string]interface{}) error { if params == nil || len(params) == 0 { return nil @@ -196,8 +200,9 @@ func (p *Base) preExpandCheckCapacity(ctx context.Context, } func (p *Base) getSnapshotReturnInfo(snapshot map[string]interface{}, snapshotSize int64) map[string]interface{} { - snapshotCreated := utils.ParseIntWithDefault(snapshot["TIMESTAMP"].(string), 10, 64, 0) - snapshotSizeBytes := snapshotSize * 512 + snapshotCreated := utils.ParseIntWithDefault(snapshot["TIMESTAMP"].(string), + constants.DefaultIntBase, constants.DefaultIntBitSize, 0) + snapshotSizeBytes := snapshotSize * constants.AllocationUnitBytes return map[string]interface{}{ "CreationTime": snapshotCreated, "SizeBytes": snapshotSizeBytes, @@ -268,3 +273,24 @@ func (p *Base) prepareVolObj(ctx context.Context, params, res map[string]interfa } return volObj } + +func (p *Base) getMetroPairSyncSpeed(_ context.Context, params map[string]interface{}) error { + if params == nil { + return nil + } + + hyper, exist := params["hypermetro"].(bool) + if !exist || !hyper { + return nil + } + + if v, exist := params["metropairsyncspeed"].(string); exist && v != "" { + speed, err := strconv.Atoi(v) + if err != nil || speed < client.MetroPairSyncSpeedLow || speed > client.MetroPairSyncSpeedHighest { + return fmt.Errorf("error config %s for metroPairSyncSpeed", v) + } + params["metropairsyncspeed"] = speed + } + + return nil +} diff --git a/storage/oceanstor/volume/creator/base.go b/storage/oceanstor/volume/creator/base.go index ad35f797..34320048 100644 --- a/storage/oceanstor/volume/creator/base.go +++ b/storage/oceanstor/volume/creator/base.go @@ -26,8 +26,8 @@ import ( "huawei-csi-driver/storage/oceanstor/client" "huawei-csi-driver/storage/oceanstor/smartx" "huawei-csi-driver/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" ) const ( @@ -38,7 +38,7 @@ const ( // BaseCreator provides some common methods for volume creation. type BaseCreator struct { cli client.BaseClientInterface - transaction *taskflow.Transaction + transaction *flow.Transaction // common fields of the filesystem vStoreId string @@ -67,11 +67,12 @@ type BaseCreator struct { domainId string isPairOnlineDelete bool isSyncPair bool + metroPairSyncSpeed int } // Init initiates fields of BaseCreator func (c *BaseCreator) Init(params *Parameter) { - c.transaction = taskflow.NewTransaction() + c.transaction = flow.NewTransaction() c.fsName = params.PvcName() c.storagePoolName = params.StoragePool() c.storagePoolId = params.PoolID() @@ -87,6 +88,7 @@ func (c *BaseCreator) Init(params *Parameter) { c.accessKrb5p = params.AccessKrb5p() c.domainId = params.MetroDomainID() c.vStorePairId = params.VStorePairId() + c.metroPairSyncSpeed = params.SyncMetroPairSpeed() if !params.IsSkipNfsShareAndQos() { c.isCreateNfsShare = true @@ -401,12 +403,14 @@ func (c *BaseCreator) createHyperMetroPair(ctx context.Context, "HCRESOURCETYPE": filesystemHCRESourceType, "LOCALOBJID": activeFsId, "REMOTEOBJID": standbyFsId, - "SPEED": highestPairSpeed, "VSTOREPAIRID": c.vStorePairId, } if c.domainId != "" { req["DOMAINID"] = c.domainId } + if c.metroPairSyncSpeed != 0 { + req["SPEED"] = c.metroPairSyncSpeed + } pair, err := c.cli.CreateHyperMetroPair(ctx, req) if err != nil { return "", fmt.Errorf("create nas hypermetro pair error: %w", err) diff --git a/storage/oceanstor/volume/creator/filesystem.go b/storage/oceanstor/volume/creator/filesystem.go index c9c3a3ef..9f182006 100644 --- a/storage/oceanstor/volume/creator/filesystem.go +++ b/storage/oceanstor/volume/creator/filesystem.go @@ -59,6 +59,10 @@ func NewFsCreatorFromParams(cli client.BaseClientInterface, creator.fileSystemMode = params.FilesystemMode() } + if creator.fileSystemMode == client.HyperMetroFilesystemMode { + creator.vStoreId = creator.cli.GetvStoreID() + } + for _, opt := range opts { opt(creator) } diff --git a/storage/oceanstor/volume/creator/hypermetro_fs.go b/storage/oceanstor/volume/creator/hypermetro_fs.go index 030186f1..20510098 100644 --- a/storage/oceanstor/volume/creator/hypermetro_fs.go +++ b/storage/oceanstor/volume/creator/hypermetro_fs.go @@ -27,7 +27,6 @@ import ( const ( filesystemHCRESourceType = 2 - highestPairSpeed = 4 // hyper metro pair running status hyperMetroPairRunningStatusNormal = "1" diff --git a/storage/oceanstor/volume/creator/parameter.go b/storage/oceanstor/volume/creator/parameter.go index dc29640e..346b9f6e 100644 --- a/storage/oceanstor/volume/creator/parameter.go +++ b/storage/oceanstor/volume/creator/parameter.go @@ -88,6 +88,8 @@ const ( SnapshotParentIdKey = "snapshotparentid" // HyperMetroKey is the string of HyperMetro's key HyperMetroKey = "hypermetro" + // MetroPairSyncSpeedKey is the string of MetroPairSyncSpeed's key + MetroPairSyncSpeedKey = "metropairsyncspeed" // RemoteStoragePoolKey is the string of RemoteStoragePool's key RemoteStoragePoolKey = "remotestoragepool" // RemotePoolIdKey is the string of RemotePoolId's key @@ -214,6 +216,11 @@ func (p *Parameter) VStorePairId() string { return getValueOrFallback(p.params, // IsHyperMetro gets the HyperMetro value of the params map. func (p *Parameter) IsHyperMetro() bool { return getValueOrFallback(p.params, HyperMetroKey, false) } +// SyncMetroPairSpeed gets the SyncMetroPairSpeed value of the params map. +func (p *Parameter) SyncMetroPairSpeed() int { + return getValueOrFallback(p.params, MetroPairSyncSpeedKey, 0) +} + // IsReplication gets the Replication value of the params map. func (p *Parameter) IsReplication() bool { return getValueOrFallback(p.params, ReplicationKey, false) } diff --git a/storage/oceanstor/volume/dtree.go b/storage/oceanstor/volume/dtree.go index 8c546146..b1866a47 100644 --- a/storage/oceanstor/volume/dtree.go +++ b/storage/oceanstor/volume/dtree.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,15 @@ import ( "huawei-csi-driver/storage/oceanstor/client" "huawei-csi-driver/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" +) + +const ( + accessKrb5ReadOnly = 0 + accessKrb5ReadWrite = 1 + accessKrb5None = 5 + accessKrb5Default = -1 ) // DTree provides base DTree client @@ -98,7 +105,7 @@ func (p *DTree) Create(ctx context.Context, params map[string]interface{}) (util return nil, err } - taskFlow := taskflow.NewTaskFlow(ctx, "Create-FileSystem-DTree-Volume") + taskFlow := flow.NewTaskFlow(ctx, "Create-FileSystem-DTree-Volume") taskFlow.AddTask("Check-FS", p.checkFSExist, nil) taskFlow.AddTask("Create-DTree", p.createDtree, p.revertDtree) taskFlow.AddTask("Create-Share", p.createShare, p.revertShare) @@ -119,7 +126,7 @@ func (p *DTree) Create(ctx context.Context, params map[string]interface{}) (util func (p *DTree) Delete(ctx context.Context, params map[string]interface{}) error { var err error - taskFlow := taskflow.NewTaskFlow(ctx, "Delete-FileSystem-DTree-Volume") + taskFlow := flow.NewTaskFlow(ctx, "Delete-FileSystem-DTree-Volume") taskFlow.AddTask("Check-DTree", p.checkDtreeExist, nil) taskFlow.AddTask("Delete-Quota", p.deleteQuota, nil) taskFlow.AddTask("Delete-Share", p.deleteShare, nil) @@ -135,8 +142,7 @@ func (p *DTree) Delete(ctx context.Context, params map[string]interface{}) error } // Expand expands volume size -func (p *DTree) Expand(ctx context.Context, parentName, dTreeName, vstoreID string, spaceSoftQuota, - spaceHardQuota int64) error { +func (p *DTree) Expand(ctx context.Context, parentName, dTreeName, vstoreID string, spaceHardQuota int64) error { dTreeID, err := p.getDtreeID(ctx, parentName, vstoreID, dTreeName) if err != nil { return err @@ -308,7 +314,7 @@ func (p *DTree) deleteDtree(ctx context.Context, params, return nil, err } -func (p *DTree) allowShareAccess(ctx context.Context, params, taskResult map[string]interface{}) (map[string]interface{}, error) { +func (p *DTree) allowShareAccess(ctx context.Context, params, taskResult map[string]any) (map[string]any, error) { shareID, _ := utils.ToStringWithFlag(taskResult["shareId"]) authClient, _ := utils.ToStringWithFlag(params["authclient"]) vStoreID, _ := utils.ToStringWithFlag(params["vstoreid"]) @@ -376,8 +382,8 @@ func (p *DTree) getCurrentShareAccess(ctx context.Context, shareID, vStoreID str accesses := make(map[string]interface{}) var i int64 = 0 - for ; i < count; i += 100 { // Query per page 100 - clients, err := cli.GetNfsShareAccessRange(ctx, shareID, vStoreID, i, i+100) + for ; i < count; i += queryNfsSharePerPage { + clients, err := cli.GetNfsShareAccessRange(ctx, shareID, vStoreID, i, i+queryNfsSharePerPage) if err != nil { return nil, err } @@ -531,7 +537,7 @@ func (p *DTree) revertQuota(ctx context.Context, taskResult map[string]interface return nil } -func (p *DTree) deleteQuota(ctx context.Context, params, taskResult map[string]interface{}) (map[string]interface{}, error) { +func (p *DTree) deleteQuota(ctx context.Context, params, taskResult map[string]any) (map[string]any, error) { req := map[string]interface{}{ "PARENTTYPE": client.ParentTypeDTree, "PARENTID": taskResult["dTreeId"], @@ -672,20 +678,21 @@ func (p *DTree) getDtreeID(ctx context.Context, parentName, vstoreID, dTreeName func formatKerberosParam(data interface{}) int { if data == nil { - return -1 + return accessKrb5Default } str, ok := data.(string) if !ok { - return -1 + return accessKrb5Default } + switch str { case "read_only": - return 0 + return accessKrb5ReadOnly case "read_write": - return 1 + return accessKrb5ReadWrite case "none": - return 5 + return accessKrb5None default: - return -1 + return accessKrb5Default } } diff --git a/storage/oceanstor/volume/nas.go b/storage/oceanstor/volume/nas.go index 18bb34b0..1eb875c4 100644 --- a/storage/oceanstor/volume/nas.go +++ b/storage/oceanstor/volume/nas.go @@ -31,8 +31,8 @@ import ( "huawei-csi-driver/storage/oceanstor/smartx" "huawei-csi-driver/storage/oceanstor/volume/creator" "huawei-csi-driver/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" ) const ( @@ -47,6 +47,8 @@ const ( noAllSquash = 1 rootSquash = 0 noRootSquash = 1 + + queryNfsSharePerPage int64 = 100 ) // ErrLogicPortFailOver indicates an error that logic port is fail over. @@ -67,14 +69,6 @@ type NAS struct { isRunningOnOwnSite bool } -type allowNfsShareAccessParam struct { - shareID string - authClient string - vStoreID string - activeClient client.BaseClientInterface - accesses map[string]interface{} -} - // NewNAS inits a new nas client func NewNAS(cli, metroRemoteCli client.BaseClientInterface, product string, nasHyperMetro NASHyperMetro, isRunningOnOwnSite bool) *NAS { @@ -380,600 +374,6 @@ func (p *NAS) validateManageWorkLoadType(ctx context.Context, params, fs map[str return nil } -func (p *NAS) createLocalFS(ctx context.Context, params, taskResult map[string]interface{}) ( - map[string]interface{}, error) { - - fsName, ok := params["name"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert fsName to string failed, data: %v", params["name"]) - } - fs, err := p.cli.GetFileSystemByName(ctx, fsName) - if err != nil { - log.AddContext(ctx).Errorf("Get filesystem %s error: %v", fsName, err) - return nil, err - } - - var isClone bool - if fs == nil { - params["parentid"] = params["poolID"] - params["vstoreId"] = params["localVStoreID"] - - if _, exist := params["clonefrom"]; exist { - fs, err = p.clone(ctx, params) - if err != nil { - log.AddContext(ctx).Warningf("p.clone() failed, param:%+v", params) - } - isClone = true - } else if _, exist := params["fromSnapshot"]; exist { - fs, err = p.createFromSnapshot(ctx, params) - if err != nil { - log.AddContext(ctx).Warningf("p.createFromSnapshot() failed, param:%+v", params) - } - isClone = true - } else { - fs, err = p.cli.CreateFileSystem(ctx, params) - if err != nil { - log.AddContext(ctx).Warningf("CreateFileSystem() failed, param:%+v", params) - } - } - } else { - if fs["ISCLONEFS"].(string) != "false" { - fsID, ok := fs["ID"].(string) - if !ok { - log.AddContext(ctx).Warningf("convert fsID to string failed, data: %v", fs["ID"]) - } - err = p.waitFSSplitDone(ctx, fsID) - } - } - - if err != nil { - log.AddContext(ctx).Errorf("Create filesystem %s error: %v", fsName, err) - return nil, err - } - - localFSID, ok := fs["ID"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert localFSID to string failed, data: %v", fs["ID"]) - } - if err = p.updateFileSystem(ctx, isClone, localFSID, params); err != nil { - log.AddContext(ctx).Errorf("Update filesystem %s error: %v", fsName, err) - return nil, err - } - - return map[string]interface{}{ - "localFSID": localFSID, - }, nil -} - -func (p *NAS) updateFileSystem(ctx context.Context, isClone bool, objID string, params map[string]interface{}) error { - if !isClone { - return nil - } - - log.AddContext(ctx).Infof("The fileSystem %s is cloned, now to update some fields.", - params["name"].(string)) - data := make(map[string]interface{}) - if val, exist := params["reservedsnapshotspaceratio"].(int); exist { - data["SNAPSHOTRESERVEPER"] = val - } - - if val, exist := params["isshowsnapdir"].(bool); exist { - data["ISSHOWSNAPDIR"] = val - } - - if val, exist := params["description"].(string); exist { - data["DESCRIPTION"] = val - } - - if data == nil { - log.AddContext(ctx).Infof("The fileSystem %s is cloned, but no field need to update.", - params["name"].(string)) - return nil - } - - // Only update the local FS, the remote FS is created separately, no need to update - err := p.cli.UpdateFileSystem(ctx, objID, data) - if err != nil { - log.AddContext(ctx).Errorf("Update FileSystem %s field [%v], error: %v", objID, data, err) - return err - } - return nil -} - -func (p *NAS) clone(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { - clonefrom, ok := params["clonefrom"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert clonefrom to string failed, data: %v", params["clonefrom"]) - } - cloneFromFS, err := p.cli.GetFileSystemByName(ctx, clonefrom) - if err != nil { - log.AddContext(ctx).Errorf("Get clone src filesystem %s error: %v", clonefrom, err) - return nil, err - } - if cloneFromFS == nil { - msg := fmt.Errorf("Filesystem %s does not exist", clonefrom) - log.AddContext(ctx).Errorln(msg) - return nil, msg - } - - srcFSCapacity, err := strconv.ParseInt(cloneFromFS["CAPACITY"].(string), 10, 64) - if err != nil { - return nil, err - } - - cloneFSCapacity, ok := params["capacity"].(int64) - if !ok { - log.AddContext(ctx).Warningf("convert cloneFSCapacity to int64 failed, data: %v", params["capacity"]) - } - if cloneFSCapacity < srcFSCapacity { - msg := fmt.Sprintf("Clone filesystem capacity must be >= src %s", clonefrom) - log.AddContext(ctx).Errorln(msg) - return nil, errors.New(msg) - } - - cloneFilesystemReq := &CloneFilesystemRequest{ - FsName: params["name"].(string), - ParentID: cloneFromFS["ID"].(string), - ParentSnapshotID: "", - AllocType: params["alloctype"].(int), - CloneSpeed: params["clonespeed"].(int), - CloneFsCapacity: cloneFSCapacity, - SrcCapacity: srcFSCapacity, - DeleteParentSnapshot: true, - VStoreId: systemVStore, - } - cloneFS, err := p.cloneFilesystem(ctx, cloneFilesystemReq) - if err != nil { - log.AddContext(ctx).Errorf("Clone filesystem %s from source filesystem %s error: %s", - cloneFilesystemReq.FsName, cloneFilesystemReq.ParentID, err) - return nil, err - } - - return cloneFS, nil -} - -func (p *NAS) createFromSnapshot(ctx context.Context, params map[string]interface{}) (map[string]interface{}, error) { - srcSnapshotName, ok := params["fromSnapshot"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert srcSnapshotName to string failed, data: %v", params["fromSnapshot"]) - } - snapshotParentId, ok := params["snapshotparentid"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert snapshotParentId to string failed, data: %v", params["snapshotparentid"]) - } - srcSnapshot, err := p.cli.GetFSSnapshotByName(ctx, snapshotParentId, srcSnapshotName) - if err != nil { - log.AddContext(ctx).Errorf("Get src filesystem snapshot %s error: %v", srcSnapshotName, err) - return nil, err - } - if srcSnapshot == nil { - msg := fmt.Errorf("src snapshot %s does not exist", srcSnapshotName) - log.AddContext(ctx).Errorln(msg) - return nil, msg - } - - parentName, ok := srcSnapshot["PARENTNAME"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert parentName to string failed, data: %v", srcSnapshot["PARENTNAME"]) - } - parentFS, err := p.cli.GetFileSystemByName(ctx, parentName) - if err != nil { - log.AddContext(ctx).Errorf("Get clone src filesystem %s error: %v", parentName, err) - return nil, err - } - if parentFS == nil { - msg := fmt.Errorf("Filesystem %s does not exist", parentName) - log.AddContext(ctx).Errorln(msg) - return nil, msg - } - - srcSnapshotCapacity, err := strconv.ParseInt(parentFS["CAPACITY"].(string), 10, 64) - if err != nil { - return nil, err - } - - cloneFilesystemReq := &CloneFilesystemRequest{ - FsName: params["name"].(string), - ParentID: srcSnapshot["PARENTID"].(string), - ParentSnapshotID: srcSnapshot["ID"].(string), - AllocType: params["alloctype"].(int), - CloneSpeed: params["clonespeed"].(int), - CloneFsCapacity: params["capacity"].(int64), - SrcCapacity: srcSnapshotCapacity, - DeleteParentSnapshot: false, - VStoreId: systemVStore, - } - cloneFS, err := p.cloneFilesystem(ctx, cloneFilesystemReq) - if err != nil { - log.AddContext(ctx).Errorf("Clone filesystem %s from source snapshot %s error: %s", - cloneFilesystemReq.FsName, cloneFilesystemReq.ParentSnapshotID, err) - return nil, err - } - - return cloneFS, nil -} - -func (p *NAS) cloneFilesystem(ctx context.Context, req *CloneFilesystemRequest) (map[string]interface{}, error) { - cloneFS, err := p.cli.CloneFileSystem(ctx, req.FsName, req.AllocType, req.ParentID, req.ParentSnapshotID) - if err != nil { - log.AddContext(ctx).Errorf("Create cloneFilesystem failed. source filesystem ID [%s], error: [%v]", - req.ParentID, err) - return nil, err - } - - cloneFSID, ok := cloneFS["ID"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert cloneFSID to string failed, data: %v", cloneFS["ID"]) - } - if req.CloneFsCapacity > req.SrcCapacity { - err := p.cli.ExtendFileSystem(ctx, cloneFSID, req.CloneFsCapacity) - if err != nil { - log.AddContext(ctx).Errorf("Extend filesystem %s to capacity %d error: %v", - cloneFSID, req.CloneFsCapacity, err) - _ = p.cli.DeleteFileSystem(ctx, map[string]interface{}{"ID": cloneFSID}) - return nil, err - } - } - - vStoreId, ok := cloneFS["vstoreId"].(string) - if ok { - req.VStoreId = vStoreId - } - - err = p.splitClone(ctx, cloneFSID, req) - if err != nil { - log.AddContext(ctx).Errorf("split clone failed. err: %v", err) - } - - return cloneFS, nil -} - -func (p *NAS) splitClone(ctx context.Context, cloneFSID string, req *CloneFilesystemRequest) error { - err := p.cli.SplitCloneFS(ctx, cloneFSID, req.VStoreId, req.CloneSpeed, req.DeleteParentSnapshot) - if err != nil { - log.AddContext(ctx).Errorf("Split filesystem [%s] error: %v", req.FsName, err) - delErr := p.cli.DeleteFileSystem(ctx, map[string]interface{}{"ID": cloneFSID}) - if delErr != nil { - log.AddContext(ctx).Errorf("Delete filesystem [%s] error: %v", cloneFSID, err) - } - return err - } - - return p.waitFSSplitDone(ctx, cloneFSID) -} - -func (p *NAS) waitFSSplitDone(ctx context.Context, fsID string) error { - return utils.WaitUntil(func() (bool, error) { - fs, err := p.cli.GetFileSystemByID(ctx, fsID) - if err != nil { - return false, err - } - - if fs["ISCLONEFS"] == "false" { - return true, nil - } - - if fs["HEALTHSTATUS"].(string) != filesystemHealthStatusNormal { - return false, fmt.Errorf("filesystem %s has the bad healthStatus code %s", fs["NAME"], fs["HEALTHSTATUS"].(string)) - } - - splitStatus, ok := fs["SPLITSTATUS"].(string) - if !ok { - return false, pkgUtils.Errorf(ctx, "convert splitStatus to string failed, data: %v", fs["SPLITSTATUS"]) - } - if splitStatus == filesystemSplitStatusQueuing || - splitStatus == filesystemSplitStatusSplitting || - splitStatus == filesystemSplitStatusNotStart { - return false, nil - } else if splitStatus == filesystemSplitStatusAbnormal { - return false, fmt.Errorf("filesystem clone [%s] split status is interrupted, SPLITSTATUS: [%s]", - fs["NAME"], splitStatus) - } else { - return true, nil - } - }, time.Hour*6, time.Second*5) -} - -func (p *NAS) revertLocalFS(ctx context.Context, taskResult map[string]interface{}) error { - fsID, exist := taskResult["localFSID"].(string) - if !exist || fsID == "" { - return nil - } - deleteParams := map[string]interface{}{ - "ID": fsID, - } - if vStoreID, _ := taskResult["localVStoreID"].(string); vStoreID != "" { - deleteParams["vstoreId"] = vStoreID - } - return p.cli.DeleteFileSystem(ctx, deleteParams) -} - -func (p *NAS) createLocalQoS(ctx context.Context, - params, taskResult map[string]interface{}) (map[string]interface{}, error) { - qos, exist := params["qos"].(map[string]int) - if !exist { - return nil, nil - } - - activeClient := p.getActiveClient(taskResult) - smartX := smartx.NewSmartX(activeClient) - vStoreID := p.getVStoreID(taskResult) - fsID := p.getActiveFsID(taskResult) - qosID, err := smartX.CreateQos(ctx, fsID, "fs", vStoreID, qos) - if err != nil { - log.AddContext(ctx).Errorf("Create qos %v for fs %s error: %v", qos, fsID, err) - return nil, err - } - - return map[string]interface{}{ - "localQoSID": qosID, - }, nil -} - -func (p *NAS) revertLocalQoS(ctx context.Context, taskResult map[string]interface{}) error { - fsID, fsIDExist := taskResult["localFSID"].(string) - qosID, qosIDExist := taskResult["localQoSID"].(string) - if !fsIDExist || !qosIDExist { - return nil - } - - activeClient := p.getActiveClient(taskResult) - smartX := smartx.NewSmartX(activeClient) - vStoreID := p.getVStoreID(taskResult) - fsID = p.getActiveFsID(taskResult) - return smartX.DeleteQos(ctx, qosID, fsID, "fs", vStoreID) -} - -func (p *NAS) createRemoteQoS(ctx context.Context, - params, taskResult map[string]interface{}) (map[string]interface{}, error) { - if p.product == "DoradoV6" { - return nil, nil - } - - qos, exist := params["qos"].(map[string]int) - if !exist { - return nil, nil - } - - fsID, ok := taskResult["remoteFSID"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert fsID to string failed, data: %v", taskResult["remoteFSID"]) - } - remoteCli, ok := taskResult["remoteCli"].(client.BaseClientInterface) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert remoteCli to BaseClientInterface failed, data: %v", taskResult["remoteCli"]) - } - - smartX := smartx.NewSmartX(remoteCli) - qosID, err := smartX.CreateQos(ctx, fsID, "fs", "", qos) - if err != nil { - log.AddContext(ctx).Errorf("Create qos %v for fs %s error: %v", qos, fsID, err) - return nil, err - } - - return map[string]interface{}{ - "remoteQoSID": qosID, - }, nil -} - -func (p *NAS) revertRemoteQoS(ctx context.Context, taskResult map[string]interface{}) error { - if p.product == "DoradoV6" { - return nil - } - - fsID, fsIDExist := taskResult["remoteFSID"].(string) - qosID, qosIDExist := taskResult["remoteQoSID"].(string) - if !fsIDExist || !qosIDExist { - return nil - } - remoteCli, ok := taskResult["remoteCli"].(client.BaseClientInterface) - if !ok { - return pkgUtils.Errorf(ctx, "convert remoteCli to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) - } - smartX := smartx.NewSmartX(remoteCli) - return smartX.DeleteQos(ctx, qosID, fsID, "fs", "") -} - -func (p *NAS) createShare(ctx context.Context, - params, taskResult map[string]interface{}) (map[string]interface{}, error) { - fsName, ok := params["name"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert fsName to string failed, data: %v", params["name"]) - } - sharePath := utils.GetSharePath(fsName) - activeClient := p.getActiveClient(taskResult) - vStoreID := p.getVStoreID(taskResult) - share, err := activeClient.GetNfsShareByPath(ctx, sharePath, vStoreID) - if err != nil { - log.AddContext(ctx).Errorf("Get nfs share by path %s error: %v", sharePath, err) - return nil, err - } - - if share == nil { - fsID := p.getActiveFsID(taskResult) - shareParams := map[string]interface{}{ - "sharepath": sharePath, - "fsid": fsID, - "description": params["description"].(string), - "vStoreID": vStoreID, - } - - share, err = activeClient.CreateNfsShare(ctx, shareParams) - if err != nil { - log.AddContext(ctx).Errorf("Create nfs share %v error: %v", shareParams, err) - return nil, err - } - } - - return map[string]interface{}{ - "shareID": share["ID"].(string), - }, nil -} - -func (p *NAS) revertShare(ctx context.Context, taskResult map[string]interface{}) error { - shareID, exist := taskResult["shareID"].(string) - if !exist || len(shareID) == 0 { - return nil - } - activeClient := p.getActiveClient(taskResult) - vStoreID := p.getVStoreID(taskResult) - return activeClient.DeleteNfsShare(ctx, shareID, vStoreID) -} - -func (p *NAS) getCurrentShareAccess(ctx context.Context, shareID, vStoreID string, cli client.BaseClientInterface) (map[string]interface{}, error) { - count, err := cli.GetNfsShareAccessCount(ctx, shareID, vStoreID) - if err != nil { - return nil, err - } - - accesses := make(map[string]interface{}) - - var i int64 = 0 - for ; i < count; i += 100 { // Query per page 100 - clients, err := cli.GetNfsShareAccessRange(ctx, shareID, vStoreID, i, i+100) - if err != nil { - return nil, err - } - if clients == nil { - break - } - - for _, c := range clients { - client, ok := c.(map[string]interface{}) - if !ok { - log.AddContext(ctx).Warningf("convert client to map failed, data: %v", c) - continue - } - name, ok := client["NAME"].(string) - if !ok { - log.AddContext(ctx).Warningf("convert client name to string failed, data: %v", client["NAME"]) - continue - } - accesses[name] = c - } - } - - return accesses, nil -} - -func (p *NAS) allowShareAccess(ctx context.Context, - params, taskResult map[string]interface{}) (map[string]interface{}, error) { - allowShareAccessParam, err := p.preShareAccessParam(ctx, params, taskResult) - if err != nil { - return nil, err - } - - for _, i := range strings.Split(allowShareAccessParam.authClient, ";") { - if _, exist := allowShareAccessParam.accesses[i]; exist { - delete(allowShareAccessParam.accesses, i) - continue - } - - req := &client.AllowNfsShareAccessRequest{ - Name: i, - ParentID: allowShareAccessParam.shareID, - AccessVal: 1, - Sync: 0, - AllSquash: params["allsquash"].(int), - RootSquash: params["rootsquash"].(int), - VStoreID: allowShareAccessParam.vStoreID, - AccessKrb5: formatKerberosParam(params["accesskrb5"]), - AccessKrb5i: formatKerberosParam(params["accesskrb5i"]), - AccessKrb5p: formatKerberosParam(params["accesskrb5p"]), - } - if err = allowShareAccessParam.activeClient.AllowNfsShareAccess(ctx, req); err != nil { - log.AddContext(ctx).Errorf("Allow nfs share access %v failed. error: %v", req, err) - return nil, err - } - } - - // Remove all other extra access - for _, i := range allowShareAccessParam.accesses { - access, ok := i.(map[string]interface{}) - if !ok { - log.AddContext(ctx).Warningf("convert access to map failed, data: %v", i) - continue - } - accessID, ok := access["ID"].(string) - if !ok { - log.AddContext(ctx).Warningf("convert accessID to string failed, data: %v", access["ID"]) - continue - } - if err = allowShareAccessParam.activeClient.DeleteNfsShareAccess(ctx, accessID, - allowShareAccessParam.vStoreID); err != nil { - log.AddContext(ctx).Warningf("Delete extra nfs share access %s error: %v", accessID, err) - } - } - - return map[string]interface{}{ - "authClient": allowShareAccessParam.authClient, - }, nil -} - -func (p *NAS) preShareAccessParam(ctx context.Context, params, - taskResult map[string]interface{}) (*allowNfsShareAccessParam, error) { - var res allowNfsShareAccessParam - var err error - var b bool - res.shareID, b = taskResult["shareID"].(string) - if !b { - return nil, pkgUtils.Errorf(ctx, "convert shareID to string failed, data: %v", - taskResult["shareID"]) - } - res.authClient, b = params["authclient"].(string) - if !b { - return nil, pkgUtils.Errorf(ctx, "convert authClient to string failed, data: %v", - params["authclient"]) - } - res.activeClient = p.getActiveClient(taskResult) - res.vStoreID = p.getVStoreID(taskResult) - res.accesses, err = p.getCurrentShareAccess(ctx, res.shareID, res.vStoreID, res.activeClient) - if err != nil { - return nil, pkgUtils.Errorf(ctx, "Get current access of share %s error: %v", res.shareID, err) - } - return &res, nil -} - -func (p *NAS) revertShareAccess(ctx context.Context, taskResult map[string]interface{}) error { - shareID, ok := taskResult["shareID"].(string) - if !ok { - return pkgUtils.Errorf(ctx, "convert shareID to string failed, data: %v", taskResult["shareID"]) - } - authClient, exist := taskResult["authClient"].(string) - if !exist { - return nil - } - - activeClient := p.getActiveClient(taskResult) - vStoreID := p.getVStoreID(taskResult) - accesses, err := p.getCurrentShareAccess(ctx, shareID, vStoreID, activeClient) - if err != nil { - log.AddContext(ctx).Errorf("Get current access of share %s error: %v", shareID, err) - return err - } - - for _, i := range strings.Split(authClient, ";") { - if _, exist := accesses[i]; !exist { - continue - } - access, ok := accesses[i].(map[string]interface{}) - if !ok { - log.AddContext(ctx).Warningf("convert access to map failed, data: %v", accesses[i]) - continue - } - accessID, ok := access["ID"].(string) - if !ok { - log.AddContext(ctx).Warningf("convert accessID to string failed, data: %v", access["ID"]) - continue - } - err := p.cli.DeleteNfsShareAccess(ctx, accessID, vStoreID) - if err != nil { - log.AddContext(ctx).Warningf("Delete extra nfs share access %s error: %v", accessID, err) - } - } - return nil -} - // Query queries volume by name func (p *NAS) Query(ctx context.Context, fsName string, params map[string]interface{}) (utils.Volume, error) { fs, err := p.cli.GetFileSystemByName(ctx, fsName) @@ -993,8 +393,9 @@ func (p *NAS) Query(ctx context.Context, fsName string, params map[string]interf volObj := utils.NewVolume(fsName) // set the size, need to trans Sectors to Bytes - if capacity, err := strconv.ParseInt(fs["CAPACITY"].(string), 10, 64); err == nil { - volObj.SetSize(utils.TransK8SCapacity(capacity, 512)) + if capacity, err := strconv.ParseInt(fs["CAPACITY"].(string), + constants.DefaultIntBase, constants.DefaultIntBitSize); err == nil { + volObj.SetSize(utils.TransK8SCapacity(capacity, constants.AllocationUnitBytes)) } if fileSystemMode, ok := fs["fileSystemMode"].(string); ok { volObj.SetFilesystemMode(fileSystemMode) @@ -1032,7 +433,7 @@ func (p *NAS) Delete(ctx context.Context, fsName string) error { if err != nil { return err } - taskflow := taskflow.NewTaskFlow(ctx, "Delete-FileSystem-Volume") + taskflow := flow.NewTaskFlow(ctx, "Delete-FileSystem-Volume") if len(hyperMetroIDs) > 0 { if p.metroRemoteCli == nil { return errors.New("hyper metro backend is not configured") @@ -1086,7 +487,7 @@ func (p *NAS) Expand(ctx context.Context, fsName string, newSize int64) error { if err != nil { return err } - expandTask := taskflow.NewTaskFlow(ctx, "Expand-FileSystem-Volume") + expandTask := flow.NewTaskFlow(ctx, "Expand-FileSystem-Volume") expandTask.AddTask("Expand-PreCheck-Capacity", p.preExpandCheckCapacity, nil) if len(hyperMetroIDs) > 0 { @@ -1135,15 +536,15 @@ func (p *NAS) preExpandCheckCapacity(ctx context.Context, }, nil } -func (p *NAS) createRemoteFS(ctx context.Context, - params, taskResult map[string]interface{}) (map[string]interface{}, error) { +func (p *NAS) createRemoteFS(ctx context.Context, params, taskResult map[string]any) (map[string]any, error) { fsName, ok := params["name"].(string) if !ok { return nil, pkgUtils.Errorf(ctx, "convert fsName to string failed, data: %v", params["name"]) } remoteCli, ok := taskResult["remoteCli"].(client.BaseClientInterface) if !ok { - return nil, pkgUtils.Errorf(ctx, "convert remoteCli to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) + return nil, pkgUtils.Errorf(ctx, + "convert remoteCli to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) } fs, err := remoteCli.GetFileSystemByName(ctx, fsName) @@ -1167,7 +568,7 @@ func (p *NAS) createRemoteFS(ctx context.Context, } } - return map[string]interface{}{ + return map[string]any{ "remoteFSID": fs["ID"].(string), }, nil } @@ -1179,7 +580,8 @@ func (p *NAS) revertRemoteFS(ctx context.Context, taskResult map[string]interfac } remoteCli, ok := taskResult["remoteCli"].(client.BaseClientInterface) if !ok { - return pkgUtils.Errorf(ctx, "convert remoteCli to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) + return pkgUtils.Errorf(ctx, + "convert remoteCli to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) } deleteParams := map[string]interface{}{ "ID": fsID, @@ -1362,74 +764,6 @@ func (p *NAS) getHyperMetroParams(ctx context.Context, }, nil } -func (p *NAS) createHyperMetro(ctx context.Context, - params, taskResult map[string]interface{}) (map[string]interface{}, error) { - vStorePairID, ok := params["vstorepairid"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert vStorePairID to string failed, data: %v", params["vstorepairid"]) - } - localFSID, ok := taskResult["localFSID"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert localFSID to string failed, data: %v", taskResult["localFSID"]) - } - remoteFSID, ok := taskResult["remoteFSID"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert remoteFSID to string failed, data: %v", taskResult["remoteFSID"]) - } - activeClient := p.getActiveClient(taskResult) - if activeClient != p.cli { - localFSID, ok = taskResult["remoteFSID"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert localFSID to string failed, data: %v", taskResult["remoteFSID"]) - } - - remoteFSID, ok = taskResult["localFSID"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert remoteFSID to string failed, data: %v", taskResult["localFSID"]) - } - } - - data := map[string]interface{}{ - "HCRESOURCETYPE": 2, // 2: file system - "LOCALOBJID": localFSID, - "REMOTEOBJID": remoteFSID, - "SPEED": 4, // 4: highest speed - "VSTOREPAIRID": vStorePairID, - } - - metroDomainID, exist := params["metroDomainID"].(string) - if exist && metroDomainID != "" { - data["DOMAINID"] = metroDomainID - } - - pair, err := activeClient.CreateHyperMetroPair(ctx, data) - if err != nil { - log.AddContext(ctx).Errorf("Create nas hypermetro pair error: %v", err) - return nil, err - } - - pairID, ok := pair["ID"].(string) - if !ok { - return nil, pkgUtils.Errorf(ctx, "convert pairID to string failed, data: %v", pair["ID"]) - } - // There is no need to synchronize when use NAS Dorado V6 or OceanStor V6 HyperMetro Volume - if p.product != constants.OceanStorDoradoV6 { - err = activeClient.SyncHyperMetroPair(ctx, pairID) - if err != nil { - log.AddContext(ctx).Errorf("Sync nas hypermetro pair %s error: %v", pairID, err) - delErr := activeClient.DeleteHyperMetroPair(ctx, pairID, true) - if delErr != nil { - log.AddContext(ctx).Errorf("delete hypermetro pair %s error: %v", pairID, err) - } - return nil, err - } - } - - return map[string]interface{}{ - "hyperMetroPairID": pairID, - }, nil -} - func (p *NAS) revertHyperMetro(ctx context.Context, taskResult map[string]interface{}) error { pairID, exist := taskResult["hyperMetroPairID"].(string) if !exist { @@ -1503,7 +837,8 @@ func (p *NAS) DeleteHyperMetro(ctx context.Context, return nil, nil } -func (p *NAS) waitHyperMetroPairDeleted(ctx context.Context, pairID string, activeClient client.BaseClientInterface) error { +func (p *NAS) waitHyperMetroPairDeleted(ctx context.Context, + pairID string, activeClient client.BaseClientInterface) error { var err error if p.product == "DoradoV6" { err = activeClient.DeleteHyperMetroPair(ctx, pairID, false) @@ -1669,9 +1004,7 @@ func (p *NAS) CreateSnapshot(ctx context.Context, fsName, snapshotName string) ( return nil, err } - const capacityBase = 10 - const capacityBitSize = 64 - snapshotSize, err := strconv.ParseInt(fs.CAPACITY, capacityBase, capacityBitSize) + snapshotSize, err := strconv.ParseInt(fs.CAPACITY, constants.DefaultIntBase, constants.DefaultIntBitSize) if err != nil { log.AddContext(ctx).Errorf("parse filesystem failed. err:%v, CAPACITY: %v", err, fs.CAPACITY) return nil, err diff --git a/storage/oceanstor/volume/san.go b/storage/oceanstor/volume/san.go index b628d518..07811c89 100644 --- a/storage/oceanstor/volume/san.go +++ b/storage/oceanstor/volume/san.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,12 +24,18 @@ import ( "strconv" "time" + "huawei-csi-driver/pkg/constants" pkgUtils "huawei-csi-driver/pkg/utils" "huawei-csi-driver/storage/oceanstor/client" "huawei-csi-driver/storage/oceanstor/smartx" "huawei-csi-driver/utils" + "huawei-csi-driver/utils/flow" "huawei-csi-driver/utils/log" - "huawei-csi-driver/utils/taskflow" +) + +const ( + waitUntilTimeout = 6 * time.Hour + waitUntilInterval = 5 * time.Second ) // SAN provides base san client @@ -84,7 +90,7 @@ func (p *SAN) Create(ctx context.Context, params map[string]interface{}) (utils. return nil, err } - taskflow := taskflow.NewTaskFlow(ctx, "Create-LUN-Volume") + taskflow := flow.NewTaskFlow(ctx, "Create-LUN-Volume") hyperMetro, hyperMetroOK := params["hypermetro"].(bool) if hyperMetroOK && hyperMetro { @@ -127,8 +133,9 @@ func (p *SAN) Query(ctx context.Context, name string) (utils.Volume, error) { volObj.SetLunWWN(lunWWN) } // set the size, need to trans Sectors to Bytes - if capacity, err := strconv.ParseInt(lun["CAPACITY"].(string), 10, 64); err == nil { - volObj.SetSize(utils.TransK8SCapacity(capacity, 512)) + if capacity, err := strconv.ParseInt(lun["CAPACITY"].(string), + constants.DefaultIntBase, constants.DefaultIntBitSize); err == nil { + volObj.SetSize(utils.TransK8SCapacity(capacity, constants.AllocationUnitBytes)) } return volObj, nil @@ -156,7 +163,7 @@ func (p *SAN) Delete(ctx context.Context, name string) error { if err != nil { return pkgUtils.Errorf(ctx, "Unmarshal san HASRSSOBJECT failed, data: %v, err: %v", rssStr, err) } - taskflow := taskflow.NewTaskFlow(ctx, "Delete-LUN-Volume") + taskflow := flow.NewTaskFlow(ctx, "Delete-LUN-Volume") if hyperMetro, ok := rss["HyperMetro"]; ok && hyperMetro == "TRUE" { taskflow.AddTask("Delete-HyperMetro", p.deleteHyperMetro, nil) taskflow.AddTask("Delete-HyperMetro-Remote-LUN", p.deleteHyperMetroRemoteLun, nil) @@ -213,7 +220,7 @@ func (p *SAN) Expand(ctx context.Context, name string, newSize int64) (bool, err if err != nil { return false, pkgUtils.Errorf(ctx, "Unmarshal HASHSSOBJECT failed, error: %v", err) } - expandTask := taskflow.NewTaskFlow(ctx, "Expand-LUN-Volume") + expandTask := flow.NewTaskFlow(ctx, "Expand-LUN-Volume") expandTask.AddTask("Expand-PreCheck-Capacity", p.preExpandCheckCapacity, nil) if hyperMetro, ok := rss["HyperMetro"]; ok && hyperMetro == "TRUE" { @@ -843,7 +850,7 @@ func (p *SAN) waitLunCopyFinish(ctx context.Context, lunCopyName string) error { } else { return true, nil } - }, time.Hour*6, time.Second*5) + }, waitUntilTimeout, waitUntilInterval) if err != nil { return err @@ -883,7 +890,7 @@ func (p *SAN) waitClonePairFinish(ctx context.Context, clonePairID string) error } else { return false, fmt.Errorf("ClonePair %s running status is abnormal", clonePairID) } - }, time.Hour*6, time.Second*5) + }, waitUntilTimeout, waitUntilInterval) if err != nil { return err @@ -930,7 +937,8 @@ func (p *SAN) createRemoteLun(ctx context.Context, } remoteCli, ok := taskResult["remoteCli"].(client.BaseClientInterface) if !ok { - return nil, pkgUtils.Errorf(ctx, "remoteCli convert to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) + return nil, pkgUtils.Errorf(ctx, + "remoteCli convert to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) } lun, err := remoteCli.GetLunByName(ctx, lunName) @@ -965,7 +973,8 @@ func (p *SAN) revertRemoteLun(ctx context.Context, taskResult map[string]interfa } remoteCli, ok := taskResult["remoteCli"].(client.BaseClientInterface) if !ok { - return pkgUtils.Errorf(ctx, "remoteCli convert to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) + return pkgUtils.Errorf(ctx, + "remoteCli convert to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) } return remoteCli.DeleteLun(ctx, lunID) } @@ -984,7 +993,8 @@ func (p *SAN) createRemoteQoS(ctx context.Context, remoteCli, ok := taskResult["remoteCli"].(client.BaseClientInterface) if !ok { - return nil, pkgUtils.Errorf(ctx, "remoteCli convert to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) + return nil, pkgUtils.Errorf(ctx, + "remoteCli convert to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) } lun, err := remoteCli.GetLunByID(ctx, lunID) if err != nil { @@ -1014,7 +1024,8 @@ func (p *SAN) revertRemoteQoS(ctx context.Context, taskResult map[string]interfa } remoteCli, ok := taskResult["remoteCli"].(client.BaseClientInterface) if !ok { - return pkgUtils.Errorf(ctx, "remoteCli convert to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) + return pkgUtils.Errorf(ctx, + "remoteCli convert to client.BaseClientInterface failed, data: %v", taskResult["remoteCli"]) } smartX := smartx.NewSmartX(remoteCli) return smartX.DeleteQos(ctx, qosID, lunID, "lun", "") @@ -1129,7 +1140,7 @@ func (p *SAN) waitHyperMetroSyncFinish(ctx context.Context, pairID string) error } else { return true, nil } - }, time.Hour*6, time.Second*5) + }, waitUntilTimeout, waitUntilInterval) if err != nil { p.cli.StopHyperMetroPair(ctx, pairID) @@ -1501,7 +1512,7 @@ func (p *SAN) CreateSnapshot(ctx context.Context, } } - taskflow := taskflow.NewTaskFlow(ctx, "Create-LUN-Snapshot") + taskflow := flow.NewTaskFlow(ctx, "Create-LUN-Snapshot") taskflow.AddTask("Create-Snapshot", p.createSnapshot, p.revertSnapshot) taskflow.AddTask("Active-Snapshot", p.activateSnapshot, nil) @@ -1539,7 +1550,7 @@ func (p *SAN) DeleteSnapshot(ctx context.Context, snapshotName string) error { return nil } - taskflow := taskflow.NewTaskFlow(ctx, "Delete-LUN-Snapshot") + taskflow := flow.NewTaskFlow(ctx, "Delete-LUN-Snapshot") taskflow.AddTask("Deactivate-Snapshot", p.deactivateSnapshot, nil) taskflow.AddTask("Delete-Snapshot", p.deleteSnapshot, nil) @@ -1608,7 +1619,7 @@ func (p *SAN) waitSnapshotReady(ctx context.Context, snapshotName string) error } else { return false, nil } - }, time.Hour*6, time.Second*5) + }, waitUntilTimeout, waitUntilInterval) if err != nil { return err diff --git a/utils/concurrent/slice.go b/utils/concurrent/slice.go new file mode 100644 index 00000000..f3dfa91a --- /dev/null +++ b/utils/concurrent/slice.go @@ -0,0 +1,95 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package concurrent + +import ( + "fmt" + "sync" +) + +// Slice is a slice wrapper for safe concurrency +type Slice[T any] struct { + mu sync.RWMutex + slice []T +} + +// Len gets the length of Slice +func (s *Slice[T]) Len() int { + s.mu.RLock() + defer s.mu.RUnlock() + + return len(s.slice) +} + +// Get gets the value of given index, +// NOTE that if index is out of range, will return zero value of T. +func (s *Slice[T]) Get(index int) T { + s.mu.RLock() + defer s.mu.RUnlock() + + if index < 0 || index >= len(s.slice) { + var zero T + return zero + } + + return s.slice[index] +} + +// Values gets a copy values of the base slice +func (s *Slice[T]) Values() []T { + s.mu.RLock() + defer s.mu.RUnlock() + + copySlice := make([]T, 0, len(s.slice)) + for _, v := range s.slice { + copySlice = append(copySlice, v) + } + + return copySlice +} + +// Append appends elements to the end of the Slice +func (s *Slice[T]) Append(e ...T) { + s.mu.Lock() + defer s.mu.Unlock() + + s.slice = append(s.slice, e...) +} + +// Cut cuts the Slice by the given low and high index +func (s *Slice[T]) Cut(low, high int) error { + if low > high { + return fmt.Errorf("low index is greater than high index") + } + + s.mu.Lock() + defer s.mu.Unlock() + + if low < 0 || low > len(s.slice) || high < 0 || high > len(s.slice) { + return fmt.Errorf("index out of range [%d,%d] with length: %d", low, high, len(s.slice)) + } + s.slice = s.slice[low:high] + return nil +} + +// Reset sets the base slice to nil +func (s *Slice[T]) Reset() { + s.mu.Lock() + defer s.mu.Unlock() + + s.slice = nil +} diff --git a/utils/concurrent/slice_test.go b/utils/concurrent/slice_test.go new file mode 100644 index 00000000..42df239b --- /dev/null +++ b/utils/concurrent/slice_test.go @@ -0,0 +1,111 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package concurrent + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSlice_ConcurrentAppend(t *testing.T) { + // arrange + s := Slice[struct{}]{} + empty := struct{}{} + expectedLen := 10000 + var wg sync.WaitGroup + + // action + for range expectedLen { + wg.Add(1) + go func() { + s.Append(empty) + wg.Done() + }() + } + wg.Wait() + + // assert + require.Equal(t, expectedLen, s.Len()) +} + +func TestSlice_Reset(t *testing.T) { + // arrange + s := Slice[int]{slice: []int{2, 3}} + + // action + s.Reset() + s.Append(1) + + // assert + require.Equal(t, 1, s.Len()) + require.Equal(t, 1, s.Get(0)) + require.Equal(t, []int{1}, s.Values()) +} + +func TestSlice_Cut(t *testing.T) { + // arrange + s := []int{1, 2} + getSlice := func() Slice[int] { return Slice[int]{slice: s} } + cases := []struct { + name string + l int + h int + expectedValues []int + expectedErr bool + }{ + {name: "l is 0, h is 0", l: 0, h: 0, expectedValues: s[0:0], expectedErr: false}, + {name: "l is 0, h is 1", l: 0, h: 1, expectedValues: s[0:1], expectedErr: false}, + {name: "l is 0, h is 2", l: 0, h: 2, expectedValues: s[0:], expectedErr: false}, + {name: "l is 1, h is 1", l: 1, h: 1, expectedValues: s[1:1], expectedErr: false}, + {name: "l is 1, h is 2", l: 1, h: 2, expectedValues: s[1:], expectedErr: false}, + {name: "l is 2, h is 2", l: 2, h: 2, expectedValues: s[2:], expectedErr: false}, + {name: "l is 1, h is 0", l: 1, h: 0, expectedValues: s, expectedErr: true}, + {name: "l is 0, h is 3", l: 0, h: 3, expectedValues: s, expectedErr: true}, + {name: "l is 1, h is 3", l: 1, h: 3, expectedValues: s, expectedErr: true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + s := getSlice() + + // action + err := s.Cut(c.l, c.h) + + // assert + require.Equal(t, c.expectedErr, err != nil) + if err == nil { + require.Equal(t, c.expectedValues, s.Values()) + } + }) + } +} + +func TestSlice_Get(t *testing.T) { + // arrange + s := Slice[int]{slice: []int{1, 2}} + + // action + v1 := s.Get(0) + v2 := s.Get(1) + v3 := s.Get(2) + + // assert + require.Equal(t, 1, v1) + require.Equal(t, 2, v2) + require.Equal(t, 0, v3) +} diff --git a/utils/flow/taskflow.go b/utils/flow/taskflow.go new file mode 100644 index 00000000..35c987e6 --- /dev/null +++ b/utils/flow/taskflow.go @@ -0,0 +1,135 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package flow offers task flow operations +package flow + +import ( + "context" + + "huawei-csi-driver/utils" + "huawei-csi-driver/utils/log" +) + +// TaskRunFunc run task +type TaskRunFunc func(ctx context.Context, params map[string]any, result map[string]any) (map[string]any, error) + +// TaskWithoutRevert run task without revert +type TaskWithoutRevert func(ctx context.Context, params map[string]interface{}) error + +// TaskRevertFunc revert task +type TaskRevertFunc func(ctx context.Context, result map[string]interface{}) error + +// Task defines the task +type Task struct { + name string + finish bool + run TaskRunFunc + revert TaskRevertFunc +} + +// TaskFlow defines the task flow +type TaskFlow struct { + name string + tasks []*Task + result map[string]interface{} + ctx context.Context +} + +// NewTaskFlow create a task flow +func NewTaskFlow(ctx context.Context, name string) *TaskFlow { + return &TaskFlow{ + name: name, + result: make(map[string]interface{}), + ctx: ctx, + } +} + +// AddTask add a task to task flow +func (p *TaskFlow) AddTask(name string, run TaskRunFunc, revert TaskRevertFunc) { + p.tasks = append(p.tasks, &Task{ + name: name, + finish: false, + run: run, + revert: revert, + }) +} + +// Run execute tasks in the task flow +func (p *TaskFlow) Run(params map[string]interface{}) (map[string]interface{}, error) { + log.AddContext(p.ctx).Debugf("Start to run taskflow %s", p.name) + + for _, task := range p.tasks { + result, err := task.run(p.ctx, params, p.result) + if err != nil { + log.AddContext(p.ctx).Errorf("Run task %s of taskflow %s error: %v", task.name, p.name, err) + return nil, err + } + + task.finish = true + + if result != nil { + p.result = utils.MergeMap(p.result, result) + } + } + + log.AddContext(p.ctx).Debugf("Taskflow %s is finished", p.name) + return p.result, nil +} + +// GetResult get tasks execution results in the task flow +func (p *TaskFlow) GetResult() map[string]interface{} { + return p.result +} + +// Revert revert tasks in the task flow with revert function +func (p *TaskFlow) Revert() { + log.AddContext(p.ctx).Infof("Start to revert taskflow %s", p.name) + + for i := len(p.tasks) - 1; i >= 0; i-- { + task := p.tasks[i] + + if task.finish && task.revert != nil { + err := task.revert(p.ctx, p.result) + if err != nil { + log.AddContext(p.ctx).Warningf("Revert task %s of taskflow %s error: %v", task.name, p.name, err) + } + } + } + + log.AddContext(p.ctx).Infof("Taskflow %s is reverted", p.name) +} + +// AddTaskWithOutRevert be used when the task does not need revert function +func (p *TaskFlow) AddTaskWithOutRevert(run TaskWithoutRevert) *TaskFlow { + var buildFun = func(ctx context.Context, params map[string]interface{}, + _ map[string]interface{}) (map[string]interface{}, error) { + if err := run(ctx, params); err != nil { + return nil, err + } + return nil, nil + } + p.AddTask("", buildFun, nil) + return p +} + +// RunWithOutRevert run task without revert function and return only error +func (p *TaskFlow) RunWithOutRevert(params map[string]interface{}) error { + if _, err := p.Run(params); err != nil { + return err + } + return nil +} diff --git a/utils/flow/taskflow_test.go b/utils/flow/taskflow_test.go new file mode 100644 index 00000000..6331522a --- /dev/null +++ b/utils/flow/taskflow_test.go @@ -0,0 +1,88 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package flow + +import ( + "context" + "errors" + "reflect" + "testing" + + "huawei-csi-driver/utils/log" +) + +var ( + errMsg = "an error occurred while run mock fun" + + mockFun1 = func(ctx context.Context, params map[string]interface{}) error { + params["key_1"] = "value_1" + return nil + } + mockFun2 = func(ctx context.Context, params map[string]interface{}) error { + params["key_2"] = "value_2" + return nil + } + mockFun3 = func(ctx context.Context, params map[string]interface{}) error { + return errors.New(errMsg) + } +) + +const ( + logName = "taskFlowTest.log" +) + +func TestMain(m *testing.M) { + log.MockInitLogging(logName) + defer log.MockStopLogging(logName) + + m.Run() +} + +func TestAllTaskReturnSuccess(t *testing.T) { + testParams := map[string]interface{}{} + err := NewTaskFlow(context.Background(), "test_all_task_return_success"). + AddTaskWithOutRevert(mockFun1). + AddTaskWithOutRevert(mockFun2). + RunWithOutRevert(testParams) + if err != nil { + t.Errorf("an error occurred while run TestTaskWithOutRevert(), err: %v", err) + } + + result := map[string]interface{}{ + "key_1": "value_1", + "key_2": "value_2", + } + if !reflect.DeepEqual(testParams, result) { + t.Error("got an unexpected value while run TestTaskWithOutRevert()") + } +} + +func TestRunTaskFail(t *testing.T) { + testParams := map[string]interface{}{} + err := NewTaskFlow(context.Background(), "test_run_task_fail"). + AddTaskWithOutRevert(mockFun1). + AddTaskWithOutRevert(mockFun2). + AddTaskWithOutRevert(mockFun3). + RunWithOutRevert(testParams) + if err == nil { + t.Error("an error should be returned while run TestRunTaskFail()") + } + + if err.Error() != errMsg { + t.Error("got an unexpected error while run TestRunTaskFail()") + } +} diff --git a/utils/flow/transaction.go b/utils/flow/transaction.go new file mode 100644 index 00000000..2fba8e67 --- /dev/null +++ b/utils/flow/transaction.go @@ -0,0 +1,70 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package flow + +type transactionStep struct { + exec func() error + onRollback func() +} + +// Transaction implements a TCC (Try Confirm Cancel) pattern. +type Transaction struct { + stepAt int + steps []transactionStep +} + +// NewTransaction instantiate a new transaction. +func NewTransaction() *Transaction { + return &Transaction{ + steps: []transactionStep{}, + } +} + +// Then adds a step to the steps chain, and returns the same Transaction, +func (t *Transaction) Then(exec func() error, onRollback func()) *Transaction { + t.steps = append(t.steps, transactionStep{ + exec: exec, + onRollback: onRollback, + }) + return t +} + +// Commit executes the Transaction steps and returns error if any one step returns error. +func (t *Transaction) Commit() error { + var err error + + for t.stepAt < len(t.steps) { + if t.stepAt >= 0 && t.stepAt < len(t.steps) && t.steps[t.stepAt].exec != nil { + if err = t.steps[t.stepAt].exec(); err != nil { + break + } + } + + t.stepAt++ + } + + return err +} + +// Rollback executes the Transaction rollbacks. +func (t *Transaction) Rollback() { + for i := t.stepAt - 1; i >= 0; i-- { + if i < len(t.steps) && t.steps[i].onRollback != nil { + t.steps[i].onRollback() + } + } +} diff --git a/utils/flow/transaction_test.go b/utils/flow/transaction_test.go new file mode 100644 index 00000000..92f5c983 --- /dev/null +++ b/utils/flow/transaction_test.go @@ -0,0 +1,105 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package flow_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "huawei-csi-driver/utils/flow" +) + +func TestTransaction_NoError(t *testing.T) { + // arrange + var i int + transaction := flow.NewTransaction(). + Then( + func() error { + i++ + return nil + }, + func() { i-- }, + ). + Then( + func() error { + i += 2 + return nil + }, + func() { i -= 2 }, + ) + + // act + err := transaction.Commit() + + // assert + require.NoError(t, err) + require.Equal(t, 3, i) +} + +func TestTransaction_WithError(t *testing.T) { + // arrange + var i int + transaction := flow.NewTransaction(). + Then( + func() error { + return assert.AnError + }, + func() { i-- }, + ) + + // act + err := transaction.Commit() + + // assert + require.ErrorIs(t, err, assert.AnError) +} + +func TestTransaction_Rollback(t *testing.T) { + // arrange + var i int + transaction := flow.NewTransaction(). + Then( + func() error { + i++ + return nil + }, + func() { i-- }, + ). + Then( + func() error { + i += 2 + return nil + }, + func() { i -= 2 }, + ). + Then( + func() error { + return assert.AnError + }, + func() { i-- }, + ) + + // act + err := transaction.Commit() + transaction.Rollback() + + // assert + require.ErrorIs(t, err, assert.AnError) + require.Equal(t, 0, i) +} diff --git a/utils/host_test.go b/utils/host_test.go index d9491587..223c493e 100644 --- a/utils/host_test.go +++ b/utils/host_test.go @@ -22,7 +22,7 @@ import ( "path" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" "huawei-csi-driver/utils/log" ) @@ -57,47 +57,39 @@ func TestChmodFsPermission(t *testing.T) { } }() - Convey("Change target directory to 777 permission", t, func() { + t.Run("Change target directory to 777 permission", func(t *testing.T) { ChmodFsPermission(context.TODO(), targetPath, "777") fileInfo, err := os.Stat(targetPath) - if err != nil { - log.Errorf("Get file/directory [%s] info failed.", targetPath) - So(err, ShouldBeNil) - } + require.NoError(t, err) + filePerm := fileInfo.Mode().Perm() - So(filePerm, ShouldEqual, os.FileMode(0777)) + require.Equal(t, os.FileMode(0777), filePerm) }) - Convey("Change target directory to 555 permission", t, func() { + t.Run("Change target directory to 555 permission", func(t *testing.T) { ChmodFsPermission(context.TODO(), targetPath, "555") fileInfo, err := os.Stat(targetPath) - if err != nil { - log.Errorf("Get file/directory [%s] info failed.", targetPath) - So(err, ShouldBeNil) - } + require.NoError(t, err) + filePerm := fileInfo.Mode().Perm() - So(filePerm, ShouldEqual, os.FileMode(0555)) + require.Equal(t, os.FileMode(0555), filePerm) }) - Convey("Change target directory to 000 permission", t, func() { + t.Run("Change target directory to 000 permission", func(t *testing.T) { ChmodFsPermission(context.TODO(), targetPath, "000") fileInfo, err := os.Stat(targetPath) - if err != nil { - log.Errorf("Get file/directory [%s] info failed.", targetPath) - So(err, ShouldBeNil) - } + require.NoError(t, err) + filePerm := fileInfo.Mode().Perm() - So(filePerm, ShouldEqual, os.FileMode(0000)) + require.Equal(t, os.FileMode(0000), filePerm) }) - Convey("Change target directory to 456 permission", t, func() { + t.Run("Change target directory to 456 permission", func(t *testing.T) { ChmodFsPermission(context.TODO(), targetPath, "456") fileInfo, err := os.Stat(targetPath) - if err != nil { - log.Errorf("Get file/directory [%s] info failed.", targetPath) - So(err, ShouldBeNil) - } + require.NoError(t, err) + filePerm := fileInfo.Mode().Perm() - So(filePerm, ShouldEqual, os.FileMode(0456)) + require.Equal(t, os.FileMode(0456), filePerm) }) } diff --git a/utils/k8sutils/pvc_helper.go b/utils/k8sutils/pvc_helper.go index 80568ae6..658578ba 100644 --- a/utils/k8sutils/pvc_helper.go +++ b/utils/k8sutils/pvc_helper.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,14 +40,12 @@ const ( eventAdd = "add" eventUpdate = "update" eventDelete = "delete" -) - -var ( - uidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) cacheSyncPeriod = 60 * time.Second ) +var uidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) + type persistentVolumeClaimOps interface { // GetVolumeConfiguration returns PVC's volume info GetVolumeConfiguration(ctx context.Context, pvName string) (map[string]string, error) diff --git a/utils/log/console.go b/utils/log/console.go index 2a01bb74..98f37ade 100644 --- a/utils/log/console.go +++ b/utils/log/console.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ type ConsoleHook struct { // newConsoleHook creates a new log hook for writing to stdout/stderr. func newConsoleHook(logFormat logrus.Formatter) (*ConsoleHook, error) { - return &ConsoleHook{logFormat}, nil + return &ConsoleHook{formatter: logFormat}, nil } // Levels returns all supported levels diff --git a/utils/log/file.go b/utils/log/file.go index ad3ce544..24eef442 100644 --- a/utils/log/file.go +++ b/utils/log/file.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,15 @@ import ( "time" "github.com/sirupsen/logrus" + + "huawei-csi-driver/pkg/constants" ) const ( - logFilePermission = 0640 - backupTimeFormat = "20060102-150405" + logFilePermission = 0640 + logFileRootDirPermission = 0750 + rotatedLogFilePermission = 0440 + backupTimeFormat = "20060102-150405" ) // FileHook sends log entries to a file. @@ -52,7 +56,7 @@ func newFileHook(logFilePath, logFileSize string, logFormat logrus.Formatter) (* logFileRootDir := filepath.Dir(logFilePath) dir, err := os.Lstat(logFileRootDir) if os.IsNotExist(err) { - if err := os.MkdirAll(logFileRootDir, 0750); err != nil { + if err := os.MkdirAll(logFileRootDir, logFileRootDirPermission); err != nil { return nil, fmt.Errorf("could not create log directory %v. %v", logFileRootDir, err) } } @@ -110,8 +114,8 @@ func (hook *FileHook) Fire(entry *logrus.Entry) error { return nil } -// logfileNeedsRotation checks to see if a file has grown too large -func (hook *FileHook) logfileNeedsRotation() bool { +// needsRotation checks to see if a file has grown too large +func (hook *FileHook) needsRotation() bool { fileInfo, err := hook.logFileHandle.stat() if err != nil { return false @@ -122,11 +126,11 @@ func (hook *FileHook) logfileNeedsRotation() bool { // maybeDoLogfileRotation check and perform log rotation func (hook *FileHook) maybeDoLogfileRotation() error { - if hook.logfileNeedsRotation() { + if hook.needsRotation() { hook.logRotateMutex.Lock() defer hook.logRotateMutex.Unlock() - if hook.logfileNeedsRotation() { + if hook.needsRotation() { // Do the rotation. err := hook.logFileHandle.rotate() if err != nil { @@ -168,7 +172,7 @@ func (f *fileHandler) rotate() error { if err := os.Rename(f.filePath, rotatedLogFileLocation); err != nil { return fmt.Errorf("failed to create backup file. %s", err) } - if err := os.Chmod(rotatedLogFileLocation, 0440); err != nil { + if err := os.Chmod(rotatedLogFileLocation, rotatedLogFilePermission); err != nil { return fmt.Errorf("failed to chmod backup file. %s", err) } @@ -222,7 +226,7 @@ func (f *fileHandler) sortedBackupLogFiles() ([]logFileInfo, error) { continue } - logFiles = append(logFiles, logFileInfo{timestamp, f}) + logFiles = append(logFiles, logFileInfo{timestamp: timestamp, FileInfo: f}) } sort.Sort(byTimeFormat(logFiles)) @@ -258,12 +262,12 @@ func getNumInByte(logFileSize string) (int64, error) { // 3.最后一位是数字或者B // 3.1 若最后一位是数字,则直接返回 若最后一位是B,则获取前面的数字返回 if lastLetter >= "0" && lastLetter <= "9" { - sum, err = strconv.ParseInt(maxDataNum, 10, 64) + sum, err = strconv.ParseInt(maxDataNum, constants.DefaultIntBase, constants.DefaultIntBitSize) if err != nil { return 0, err } } else { - sum, err = strconv.ParseInt(maxDataNum[:len(maxDataNum)-1], 10, 64) + sum, err = strconv.ParseInt(maxDataNum[:len(maxDataNum)-1], constants.DefaultIntBase, constants.DefaultIntBitSize) if err != nil { return 0, err } diff --git a/utils/log/logger.go b/utils/log/logger.go index a1ab4598..c9b0f67d 100644 --- a/utils/log/logger.go +++ b/utils/log/logger.go @@ -1,5 +1,5 @@ /* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2023. All rights reserved. + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2024. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,17 +23,13 @@ import ( "fmt" "io/ioutil" "os" - "sync" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) -var ( - logger LoggingInterface - testInitLogger sync.Once -) +var logger LoggingInterface type key string @@ -121,8 +117,8 @@ func parseLogLevel(logLevel string) (logrus.Level, error) { } } -// LoggingRequest use to init the logging service -type LoggingRequest struct { +// Config use to init the logging service +type Config struct { LogName string LogFileSize string LoggingModule string @@ -135,7 +131,7 @@ var maxBackups uint // InitLogging configures logging. Logs are written to a log file or stdout/stderr. // Since logrus doesn't support multiple writers, each log stream is implemented as a hook. -func InitLogging(req *LoggingRequest) error { +func InitLogging(req *Config) error { var tmpLogger loggerImpl // initialize logrus in wrapper diff --git a/utils/log/logger_mock.go b/utils/log/logger_mock.go index bf64500b..d31e2404 100644 --- a/utils/log/logger_mock.go +++ b/utils/log/logger_mock.go @@ -34,7 +34,7 @@ var ( // MockInitLogging mock init the logging service func MockInitLogging(logName string) { - if err := InitLogging(&LoggingRequest{ + if err := InitLogging(&Config{ LogName: logName, LogFileSize: mockLogFileSize, LoggingModule: mockLoggingModule, diff --git a/utils/utils_test.go b/utils/utils_test.go index ff369080..f857062c 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -23,11 +23,12 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" + "huawei-csi-driver/pkg/constants" "github.com/agiledragon/gomonkey/v2" "github.com/prashantv/gostub" - . "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" @@ -167,39 +168,39 @@ func mockGetSecret(data map[string][]byte, err error) *gomonkey.Patches { } func TestGetPasswordFromSecret(t *testing.T) { - Convey("TestGetPasswordFromSecret secret is nil case", t, func() { + t.Run("TestGetPasswordFromSecret secret is nil case", func(t *testing.T) { m := mockGetSecret(nil, nil) defer m.Reset() _, err := GetPasswordFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeError) + require.Error(t, err) }) - Convey("TestGetPasswordFromSecret get secret error case", t, func() { + t.Run("TestGetPasswordFromSecret get secret error case", func(t *testing.T) { m := mockGetSecret(nil, errors.New("mock error")) defer m.Reset() _, err := GetPasswordFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeError) + require.Error(t, err) }) - Convey("TestGetPasswordFromSecret secret data is nil case", t, func() { + t.Run("TestGetPasswordFromSecret secret data is nil case", func(t *testing.T) { m := mockGetSecret(map[string][]byte{}, nil) defer m.Reset() _, err := GetPasswordFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeError) + require.Error(t, err) }) - Convey("TestGetPasswordFromSecret secret data dose not have password case", t, func() { + t.Run("TestGetPasswordFromSecret secret data dose not have password case", func(t *testing.T) { m := mockGetSecret(map[string][]byte{"user": []byte("mock-user")}, nil) defer m.Reset() _, err := GetPasswordFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeError) + require.Error(t, err) }) - Convey("TestGetPasswordFromSecret normal case", t, func() { + t.Run("TestGetPasswordFromSecret normal case", func(t *testing.T) { m := mockGetSecret(map[string][]byte{ "user": []byte("mock-user"), "password": []byte("mock-pw"), @@ -207,47 +208,47 @@ func TestGetPasswordFromSecret(t *testing.T) { defer m.Reset() pw, err := GetPasswordFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeNil) - So(pw, ShouldEqual, "mock-pw") + require.NoError(t, err) + require.Equal(t, "mock-pw", pw) }) } func TestGetCertFromSecretFailed(t *testing.T) { - Convey("TestGetCertFromSecret secret is nil case", t, func() { + t.Run("TestGetCertFromSecret secret is nil case", func(t *testing.T) { m := mockGetSecret(nil, nil) defer m.Reset() _, err := GetCertFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeError) + require.Error(t, err) }) - Convey("TestGetCertFromSecret get secret error case", t, func() { + t.Run("TestGetCertFromSecret get secret error case", func(t *testing.T) { m := mockGetSecret(nil, errors.New("mock error")) defer m.Reset() _, err := GetCertFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeError) + require.Error(t, err) }) - Convey("GetCertFromSecret secret data dose not have cert case", t, func() { + t.Run("GetCertFromSecret secret data dose not have cert case", func(t *testing.T) { m := mockGetSecret(map[string][]byte{}, nil) defer m.Reset() _, err := GetCertFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeError) + require.Error(t, err) }) } func TestGetCertFromSecretSuccess(t *testing.T) { - Convey("GetCertFromSecret normal case", t, func() { + t.Run("GetCertFromSecret normal case", func(t *testing.T) { m := mockGetSecret(map[string][]byte{ "tls.crt": []byte("mock-cert"), }, nil) defer m.Reset() pw, err := GetCertFromSecret(context.TODO(), "sec-name", "sec-namespace") - So(err, ShouldBeNil) - So(pw, ShouldResemble, []byte("mock-cert")) + require.NoError(t, err) + require.Equal(t, pw, []byte("mock-cert")) }) }