Repository: kubernetes-sigs/gcp-filestore-csi-driver
Pull request #251 (2022-04-15 06:33:01)

Multishare endpoint, description support and minor fixes
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md
3. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

**What type of PR is this?**
> Uncomment only one ` /kind <>` line, hit enter to put that in a new line, and remove leading whitespaces from that line:
>
> /kind api-change
> /kind bug
/kind cleanup
> /kind design
> /kind documentation
> /kind failing-test
> /kind feature
> /kind flake

**What this PR does / why we need it**:

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

**Special notes for your reviewer**:
1. Adds support for specifying filestore endpoint and dataplane version
2. minor bug fixes for multishare controller

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note
NONE
```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/pull/251#" title="Author self-approved">saikat-royc</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fgcp-filestore-csi-driver).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/blob/master/OWNERS)~~ [saikat-royc]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
k8s-ci-robot: @saikat-royc: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-filestore-csi-driver-e2e | 5e4b3be613335f4c5e4f5799e538700f8b9c55cd | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-filestore-csi-driver/251/pull-gcp-filestore-csi-driver-e2e/1514864915783880704/) | true | `/test pull-gcp-filestore-csi-driver-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-filestore-csi-driver&pr=251). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaikat-royc). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @saikat-royc: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-filestore-csi-driver-kubernetes-integration | 79e7925c7a1a9df142e57e9ef3ad1df2946d3406 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-filestore-csi-driver/251/pull-gcp-filestore-csi-driver-kubernetes-integration/1514865425433759744/) | false | `/test pull-gcp-filestore-csi-driver-kubernetes-integration`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-filestore-csi-driver&pr=251). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaikat-royc). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
saikat-royc: /retest
k8s-ci-robot: @saikat-royc: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-filestore-csi-driver-e2e | b373479897ba88c4c2d1552f4442acd77ad6a0ed | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-filestore-csi-driver/251/pull-gcp-filestore-csi-driver-e2e/1515057882092015616/) | true | `/test pull-gcp-filestore-csi-driver-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-filestore-csi-driver&pr=251). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaikat-royc). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
saikat-royc: /retest
k8s-ci-robot: New changes are detected. LGTM label has been removed.
k8s-ci-robot: @saikat-royc: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-gcp-filestore-csi-driver-e2e | 87a77b8185acada338e7c6e6e261c800dfed6018 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/sigs.k8s.io_gcp-filestore-csi-driver/251/pull-gcp-filestore-csi-driver-e2e/1515070576610250752/) | true | `/test pull-gcp-filestore-csi-driver-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=gcp-filestore-csi-driver&pr=251). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaikat-royc). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/gcp-filestore-csi-driver/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
saikat-royc: /retest

Patch:
diff --git a/cmd/main.go b/cmd/main.go
index 8cd1d8f4..79c497fd 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -33,14 +33,16 @@ import (
 )
 
 var (
-	endpoint            = flag.String("endpoint", "unix:/tmp/csi.sock", "CSI endpoint")
-	nodeID              = flag.String("nodeid", "", "node id")
-	runController       = flag.Bool("controller", false, "run controller service")
-	runNode             = flag.Bool("node", false, "run node service")
-	cloudConfigFilePath = flag.String("cloud-config", "", "Path to GCE cloud provider config")
-	httpEndpoint        = flag.String("http-endpoint", "", "The TCP network address where the prometheus metrics endpoint will listen (example: `:8080`). The default is empty string, which means metrics endpoint is disabled.")
-	metricsPath         = flag.String("metrics-path", "/metrics", "The HTTP path where prometheus metrics will be exposed. Default is `/metrics`.")
-	enableMultishare    = flag.Bool("enable-multishare", false, "if set to true, the driver will support multishare instance provisioning")
+	endpoint                = flag.String("endpoint", "unix:/tmp/csi.sock", "CSI endpoint")
+	nodeID                  = flag.String("nodeid", "", "node id")
+	runController           = flag.Bool("controller", false, "run controller service")
+	runNode                 = flag.Bool("node", false, "run node service")
+	cloudConfigFilePath     = flag.String("cloud-config", "", "Path to GCE cloud provider config")
+	httpEndpoint            = flag.String("http-endpoint", "", "The TCP network address where the prometheus metrics endpoint will listen (example: `:8080`). The default is empty string, which means metrics endpoint is disabled.")
+	metricsPath             = flag.String("metrics-path", "/metrics", "The HTTP path where prometheus metrics will be exposed. Default is `/metrics`.")
+	enableMultishare        = flag.Bool("enable-multishare", false, "if set to true, the driver will support multishare instance provisioning")
+	filestoreServiceEnpoint = flag.String("filestore-service-endpoint", "", "Endpoint for filestore service")
+	ecfsDescription         = flag.String("ecfs-description", "", "Filestore multishare instance descrption. ecfs-version=<version>,image-project-id=<projectid>")
 	// This is set at compile time
 	version = "unknown"
 )
@@ -58,16 +60,13 @@ func main() {
 	var meta metadata.Service
 	var mm *metrics.MetricsManager
 	if *runController {
-		if *enableMultishare {
-			klog.Fatalf("provisioning of multishare instances not supported")
-		}
 		if *httpEndpoint != "" && metrics.IsGKEComponentVersionAvailable() {
 			mm = metrics.NewMetricsManager()
 			mm.InitializeHttpHandler(*httpEndpoint, *metricsPath)
 			mm.EmitGKEComponentVersion()
 		}
 
-		provider, err = cloud.NewCloud(ctx, version, *cloudConfigFilePath)
+		provider, err = cloud.NewCloud(ctx, version, *cloudConfigFilePath, *filestoreServiceEnpoint)
 	} else {
 		if *nodeID == "" {
 			klog.Fatalf("nodeid cannot be empty for node service")
@@ -95,6 +94,7 @@ func main() {
 		MetadataService:  meta,
 		EnableMultishare: *enableMultishare,
 		Metrics:          mm,
+		EcfsDescription:  *ecfsDescription,
 	}
 
 	gcfsDriver, err := driver.NewGCFSDriver(config)
diff --git a/pkg/cloud_provider/cloud.go b/pkg/cloud_provider/cloud.go
index 3b5a8a44..ce22136c 100644
--- a/pkg/cloud_provider/cloud.go
+++ b/pkg/cloud_provider/cloud.go
@@ -51,7 +51,7 @@ type ConfigGlobal struct {
 	Zone      string `gcfg:"zone"`
 }
 
-func NewCloud(ctx context.Context, version string, configPath string) (*Cloud, error) {
+func NewCloud(ctx context.Context, version string, configPath string, filestoreServiceEndpoint string) (*Cloud, error) {
 	configFile, err := maybeReadConfig(configPath)
 	if err != nil {
 		return nil, err
@@ -67,7 +67,7 @@ func NewCloud(ctx context.Context, version string, configPath string) (*Cloud, e
 		return nil, err
 	}
 
-	file, err := file.NewGCFSService(version, client)
+	file, err := file.NewGCFSService(version, client, filestoreServiceEndpoint)
 	if err != nil {
 		return nil, fmt.Errorf("failed to initialize Filestore service: %v", err)
 	}
diff --git a/pkg/cloud_provider/file/file.go b/pkg/cloud_provider/file/file.go
index d0674f5e..473d8f5f 100644
--- a/pkg/cloud_provider/file/file.go
+++ b/pkg/cloud_provider/file/file.go
@@ -64,6 +64,7 @@ type MultishareInstance struct {
 	Labels             map[string]string
 	State              string
 	KmsKeyName         string
+	Description        string
 }
 
 type ListFilter struct {
@@ -153,7 +154,7 @@ const (
 
 var _ Service = &gcfsServiceManager{}
 
-func NewGCFSService(version string, client *http.Client) (Service, error) {
+func NewGCFSService(version string, client *http.Client, endpoint string) (Service, error) {
 	ctx := context.Background()
 	fileService, err := filev1beta1.NewService(ctx, option.WithHTTPClient(client))
 	if err != nil {
@@ -161,7 +162,9 @@ func NewGCFSService(version string, client *http.Client) (Service, error) {
 	}
 	fileService.UserAgent = fmt.Sprintf("Google Cloud Filestore CSI Driver/%s (%s %s)", version, runtime.GOOS, runtime.GOARCH)
 
-	fileMultishareService, err := filev1beta1multishare.NewService(ctx, option.WithHTTPClient(client))
+	basepath := createFilestoreEndpointUrlBasePath(endpoint)
+	glog.Infof("Using endpoint %s for multshare", createFilestoreEndpointUrlBasePath(endpoint))
+	fileMultishareService, err := filev1beta1multishare.NewService(ctx, basepath, option.WithHTTPClient(client))
 	if err != nil {
 		return nil, err
 	}
@@ -702,7 +705,8 @@ func (manager *gcfsServiceManager) ListMultishareInstances(ctx context.Context,
 
 func (manager *gcfsServiceManager) StartCreateMultishareInstanceOp(ctx context.Context, instance *MultishareInstance) (*filev1beta1multishare.Operation, error) {
 	targetinstance := &filev1beta1multishare.Instance{
-		Tier: instance.Tier,
+		MultiShareEnabled: true,
+		Tier:              instance.Tier,
 		Networks: []*filev1beta1multishare.NetworkConfig{
 			{
 				Network:         instance.Network.Name,
@@ -711,9 +715,10 @@ func (manager *gcfsServiceManager) StartCreateMultishareInstanceOp(ctx context.C
 				ConnectMode:     instance.Network.ConnectMode,
 			},
 		},
-		CapacityGb: util.BytesToGb(instance.CapacityBytes),
-		KmsKeyName: instance.KmsKeyName,
-		Labels:     instance.Labels,
+		CapacityGb:  util.BytesToGb(instance.CapacityBytes),
+		KmsKeyName:  instance.KmsKeyName,
+		Labels:      instance.Labels,
+		Description: instance.Description,
 	}
 
 	klog.V(4).Infof("Creating instance %q: project %q, location %q, tier %q, capacity %v, network %q, ipRange %q, connectMode %q, KmsKeyName %q, labels %v", instance.Name, instance.Project, instance.Location, targetinstance.Tier, targetinstance.CapacityGb, targetinstance.Networks[0].Network, targetinstance.Networks[0].ReservedIpRange, targetinstance.Networks[0].ConnectMode, targetinstance.KmsKeyName, targetinstance.Labels)
@@ -895,9 +900,14 @@ func cloudInstanceToMultishareInstance(instance *filev1beta1multishare.Instance)
 		CapacityBytes:      instance.CapacityGb * util.Gb,
 		MaxCapacityBytes:   instance.MaxCapacityGb * util.Gb,
 		CapacityStepSizeGb: instance.CapacityStepSizeGb,
+		Description:        instance.Description,
 	}, nil
 }
 
 func shareURI(project, location, instanceName, shareName string) string {
 	return fmt.Sprintf(shareURIFmt, project, location, instanceName, shareName)
 }
+
+func createFilestoreEndpointUrlBasePath(endpoint string) string {
+	return "https://" + endpoint + "/"
+}
diff --git a/pkg/csi_driver/controller.go b/pkg/csi_driver/controller.go
index bf314c8d..24f1c42a 100644
--- a/pkg/csi_driver/controller.go
+++ b/pkg/csi_driver/controller.go
@@ -96,12 +96,13 @@ type controllerServerConfig struct {
 	enableMultishare     bool
 	multiShareController *MultishareController
 	metricsManager       *metrics.MetricsManager
+	ecfsDescription      string
 }
 
 func newControllerServer(config *controllerServerConfig) csi.ControllerServer {
 	config.ipAllocator = util.NewIPAllocator(make(map[string]bool))
 	if config.enableMultishare {
-		config.multiShareController = NewMultishareController(config.driver, config.fileService, config.cloud, config.volumeLocks)
+		config.multiShareController = NewMultishareController(config.driver, config.fileService, config.cloud, config.volumeLocks, config.ecfsDescription)
 	}
 	return &controllerServer{config: config}
 }
diff --git a/pkg/csi_driver/gcfs_driver.go b/pkg/csi_driver/gcfs_driver.go
index 081d7a2b..70707253 100644
--- a/pkg/csi_driver/gcfs_driver.go
+++ b/pkg/csi_driver/gcfs_driver.go
@@ -41,6 +41,7 @@ type GCFSDriverConfig struct {
 	MetadataService  metadataservice.Service
 	EnableMultishare bool
 	Metrics          *metrics.MetricsManager
+	EcfsDescription  string
 }
 
 type GCFSDriver struct {
@@ -107,6 +108,7 @@ func NewGCFSDriver(config *GCFSDriverConfig) (*GCFSDriver, error) {
 			volumeLocks:      util.NewVolumeLocks(),
 			enableMultishare: config.EnableMultishare,
 			metricsManager:   config.Metrics,
+			ecfsDescription:  config.EcfsDescription,
 		})
 	}
 
diff --git a/pkg/csi_driver/multishare_controller.go b/pkg/csi_driver/multishare_controller.go
index f8e1b8be..85524f40 100644
--- a/pkg/csi_driver/multishare_controller.go
+++ b/pkg/csi_driver/multishare_controller.go
@@ -25,6 +25,7 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 	"k8s.io/apimachinery/pkg/util/uuid"
+	"k8s.io/klog"
 	cloud "sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider"
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/cloud_provider/file"
 	"sigs.k8s.io/gcp-filestore-csi-driver/pkg/util"
@@ -33,26 +34,29 @@ import (
 const (
 	modeMultishare = "modeMultishare"
 
-	methodCreateVolume = "CreateVolume"
-	methodDeleteVolume = "DeleteVolume"
+	methodCreateVolume         = "CreateVolume"
+	methodDeleteVolume         = "DeleteVolume"
+	ecfsDataPlaneVersionFormat = "GoogleReserved-CustomVMImage=clh.image.ems.path:projects/%s/global/images/ems-filestore-scaleout-%s"
 )
 
 // MultishareController handles CSI calls for volumes which use Filestore multishare instances.
 type MultishareController struct {
-	driver      *GCFSDriver
-	fileService file.Service
-	cloud       *cloud.Cloud
-	opsManager  *MultishareOpsManager
-	volumeLocks *util.VolumeLocks
+	driver          *GCFSDriver
+	fileService     file.Service
+	cloud           *cloud.Cloud
+	opsManager      *MultishareOpsManager
+	volumeLocks     *util.VolumeLocks
+	ecfsDescription string
 }
 
-func NewMultishareController(driver *GCFSDriver, fileService file.Service, cloud *cloud.Cloud, volumeLock *util.VolumeLocks) *MultishareController {
+func NewMultishareController(driver *GCFSDriver, fileService file.Service, cloud *cloud.Cloud, volumeLock *util.VolumeLocks, ecfsDescription string) *MultishareController {
 	return &MultishareController{
-		opsManager:  NewMultishareOpsManager(cloud),
-		driver:      driver,
-		fileService: fileService,
-		cloud:       cloud,
-		volumeLocks: volumeLock,
+		opsManager:      NewMultishareOpsManager(cloud),
+		driver:          driver,
+		fileService:     fileService,
+		cloud:           cloud,
+		volumeLocks:     volumeLock,
+		ecfsDescription: ecfsDescription,
 	}
 }
 
@@ -203,7 +207,7 @@ func (m *MultishareController) ControllerExpandVolume(ctx context.Context, req *
 func getInstanceSCPrefix(req *csi.CreateVolumeRequest) (string, error) {
 	params := req.GetParameters()
 	v, ok := params[paramMultishareInstanceScLabel]
-	if ok {
+	if !ok {
 		return "", fmt.Errorf("Failed to find instance prefix key")
 	}
 
@@ -244,7 +248,7 @@ func (m *MultishareController) generateNewMultishareInstance(instanceName string
 			continue
 		case paramMultishareInstanceScLabel:
 			continue
-		case ParameterKeyLabels, ParameterKeyPVCName, ParameterKeyPVCNamespace, ParameterKeyPVName:
+		case ParameterKeyLabels, ParameterKeyPVCName, ParameterKeyPVCNamespace, ParameterKeyPVName, paramMultishare:
 		case "csiprovisionersecretname", "csiprovisionersecretnamespace":
 		default:
 			return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid parameter %q", k))
@@ -270,8 +274,9 @@ func (m *MultishareController) generateNewMultishareInstance(instanceName string
 			Name:        network,
 			ConnectMode: connectMode,
 		},
-		KmsKeyName: kmsKeyName,
-		Labels:     labels,
+		KmsKeyName:  kmsKeyName,
+		Labels:      labels,
+		Description: generateInstanceDescFromEcfsDesc(m.ecfsDescription),
 	}, nil
 }
 
@@ -423,3 +428,44 @@ func containsInstancePrefix(shareHandle string, project, location, instanceName
 	targetInstance := fmt.Sprintf("%s/%s/%s", project, location, instanceName)
 	return strings.Contains(shareHandle, targetInstance)
 }
+
+func generateInstanceDescFromEcfsDesc(desc string) string {
+	if desc == "" {
+		return desc
+	}
+
+	parts := strings.Split(desc, ",")
+	descMap := make(map[string]string)
+	for _, part := range parts {
+		pair := strings.Split(part, "=")
+		if len(pair) != 2 {
+			continue
+		}
+		descMap[pair[0]] = pair[1]
+	}
+
+	const (
+		ecfsVersionKey    = "ecfs-version"
+		imageProjectIdKey = "image-project-id"
+	)
+	var (
+		ecfsVersion    string
+		imageProjectId string
+	)
+	for k, v := range descMap {
+		switch k {
+		case ecfsVersionKey:
+			ecfsVersion = v
+		case imageProjectIdKey:
+			imageProjectId = v
+		}
+	}
+
+	if ecfsVersion == "" || imageProjectId == "" {
+		return ""
+	}
+
+	d := fmt.Sprintf(ecfsDataPlaneVersionFormat, imageProjectId, ecfsVersion)
+	klog.V(4).Infof("generated description for multishare instance %s", d)
+	return d
+}
diff --git a/vendor/google.golang.org/api/file/v1beta1multishare/file-gen.go b/vendor/google.golang.org/api/file/v1beta1multishare/file-gen.go
index 65c74092..9e595561 100755
--- a/vendor/google.golang.org/api/file/v1beta1multishare/file-gen.go
+++ b/vendor/google.golang.org/api/file/v1beta1multishare/file-gen.go
@@ -85,13 +85,17 @@ const (
 )
 
 // NewService creates a new Service.
-func NewService(ctx context.Context, opts ...option.ClientOption) (*Service, error) {
+func NewService(ctx context.Context, base string, opts ...option.ClientOption) (*Service, error) {
 	scopesOption := option.WithScopes(
 		"https://www.googleapis.com/auth/cloud-platform",
 	)
 	// NOTE: prepend, so we don't override user-specified scopes.
 	opts = append([]option.ClientOption{scopesOption}, opts...)
-	opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	if base != "" {
+		opts = append(opts, internaloption.WithDefaultEndpoint(base))
+	} else {
+		opts = append(opts, internaloption.WithDefaultEndpoint(basePath))
+	}
 	opts = append(opts, internaloption.WithDefaultMTLSEndpoint(mtlsBasePath))
 	client, endpoint, err := htransport.NewClient(ctx, opts...)
 	if err != nil {


Test patch:
diff --git a/pkg/csi_driver/multishare_controller_test.go b/pkg/csi_driver/multishare_controller_test.go
index 7d49527a..3d0b521a 100644
--- a/pkg/csi_driver/multishare_controller_test.go
+++ b/pkg/csi_driver/multishare_controller_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package driver
 
 import (
+	"fmt"
 	"reflect"
 	"testing"
 
@@ -44,7 +45,7 @@ func initTestMultishareController(t *testing.T) *MultishareController {
 	if err != nil {
 		t.Fatalf("Failed to get cloud provider: %v", err)
 	}
-	return NewMultishareController(initTestDriver(t), fileService, cloudProvider, util.NewVolumeLocks())
+	return NewMultishareController(initTestDriver(t), fileService, cloudProvider, util.NewVolumeLocks(), "")
 }
 
 func TestPickRegion(t *testing.T) {
@@ -533,3 +534,49 @@ func TestGenerateCSICreateVolumeResponse(t *testing.T) {
 		})
 	}
 }
+
+func TestGenerateInstanceDescFromEcfsDesc(t *testing.T) {
+	tests := []struct {
+		name       string
+		inputdesc  string
+		outputdesc string
+	}{
+		{
+			name: "empty",
+		},
+		{
+			name:      "invalid key value pair, unknown key",
+			inputdesc: "k1=v1",
+		},
+		{
+			name:      "invalid key value pair, unknown key1",
+			inputdesc: "k1=v1,ecfs-version=test",
+		},
+		{
+			name:      "invalid key value pair, unknown key2",
+			inputdesc: "k1=v1,image-project-id=test",
+		},
+		{
+			name:      "invalid key value pair",
+			inputdesc: "k1=v1,image-project-id",
+		},
+		{
+			name:       "case1: valid key value pair",
+			inputdesc:  "ecfs-version=ems-filestore-scaleout-3-6-0-1-70bd79ed0a91,image-project-id=elastifile-ci",
+			outputdesc: fmt.Sprintf(ecfsDataPlaneVersionFormat, "elastifile-ci", "ems-filestore-scaleout-3-6-0-1-70bd79ed0a91"),
+		},
+		{
+			name:       "case2: valid key value pair",
+			inputdesc:  "image-project-id=elastifile-ci,ecfs-version=ems-filestore-scaleout-3-6-0-1-70bd79ed0a91",
+			outputdesc: fmt.Sprintf(ecfsDataPlaneVersionFormat, "elastifile-ci", "ems-filestore-scaleout-3-6-0-1-70bd79ed0a91"),
+		},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			op := generateInstanceDescFromEcfsDesc(tc.inputdesc)
+			if op != tc.outputdesc {
+				t.Errorf("got %s, want %s", op, tc.outputdesc)
+			}
+		})
+	}
+}