TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes a Volume with AWSElasticBlockStore set from in-tree
// and converts the AWSElasticBlockStore source to a CSIPersistentVolumeSource
func (t *awsElasticBlockStoreCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil || volume.AWSElasticBlockStore == nil {
		return nil, fmt.Errorf("volume is nil or AWS EBS not defined on volume")
	}
	ebsSource := volume.AWSElasticBlockStore
	volumeHandle, err := KubernetesVolumeIDToEBSVolumeID(ebsSource.VolumeID)
	if err != nil {
		return nil, fmt.Errorf("failed to translate Kubernetes ID to EBS Volume ID %v", err)
	}
	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			// Must be unique per disk as it is used as the unique part of the
			// staging path
			Name: fmt.Sprintf("%s-%s", AWSEBSDriverName, volumeHandle),
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeSource: v1.PersistentVolumeSource{
				CSI: &v1.CSIPersistentVolumeSource{
					Driver:       AWSEBSDriverName,
					VolumeHandle: volumeHandle,
					ReadOnly:     ebsSource.ReadOnly,
					FSType:       ebsSource.FSType,
					VolumeAttributes: map[string]string{
						"partition": strconv.FormatInt(int64(ebsSource.Partition), 10),
					},
				},
			},
			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
		},
	}
	return pv, nil
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a PV with AWSElasticBlockStore set from in-tree
// and converts the AWSElasticBlockStore source to a CSIPersistentVolumeSource
func (t *awsElasticBlockStoreCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.AWSElasticBlockStore == nil {
		return nil, fmt.Errorf("pv is nil or AWS EBS not defined on pv")
	}

	ebsSource := pv.Spec.AWSElasticBlockStore

	volumeHandle, err := KubernetesVolumeIDToEBSVolumeID(ebsSource.VolumeID)
	if err != nil {
		return nil, fmt.Errorf("failed to translate Kubernetes ID to EBS Volume ID %v", err)
	}

	csiSource := &v1.CSIPersistentVolumeSource{
		Driver:       AWSEBSDriverName,
		VolumeHandle: volumeHandle,
		ReadOnly:     ebsSource.ReadOnly,
		FSType:       ebsSource.FSType,
		VolumeAttributes: map[string]string{
			"partition": strconv.FormatInt(int64(ebsSource.Partition), 10),
		},
	}

	if err := translateTopologyFromInTreeToCSI(pv, AWSEBSTopologyKey); err != nil {
		return nil, fmt.Errorf("failed to translate topology: %v", err)
	}

	pv.Spec.AWSElasticBlockStore = nil
	pv.Spec.CSI = csiSource
	return pv, nil
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and
// translates the EBS CSI source to a AWSElasticBlockStore source.
func (t *awsElasticBlockStoreCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
	}

	csiSource := pv.Spec.CSI

	ebsSource := &v1.AWSElasticBlockStoreVolumeSource{
		VolumeID: csiSource.VolumeHandle,
		FSType:   csiSource.FSType,
		ReadOnly: csiSource.ReadOnly,
	}

	if partition, ok := csiSource.VolumeAttributes["partition"]; ok {
		partValue, err := strconv.Atoi(partition)
		if err != nil {
			return nil, fmt.Errorf("failed to convert partition %v to integer: %v", partition, err)
		}
		ebsSource.Partition = int32(partValue)
	}

	// translate CSI topology to In-tree topology for rollback compatibility
	if err := translateTopologyFromCSIToInTree(pv, AWSEBSTopologyKey, getAwsRegionFromZones); err != nil {
		return nil, fmt.Errorf("failed to translate topology. PV:%+v. Error:%v", *pv, err)
	}

	pv.Spec.CSI = nil
	pv.Spec.AWSElasticBlockStore = ebsSource
	return pv, nil
}


CanSupport

// CanSupport tests whether the plugin supports a given persistent volume
// specification from the API.  The spec pointer should be considered
// const.
func (t *awsElasticBlockStoreCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
	return pv != nil && pv.Spec.AWSElasticBlockStore != nil
}


CanSupportInline

// CanSupportInline tests whether the plugin supports a given inline volume
// specification from the API.  The spec pointer should be considered
// const.
func (t *awsElasticBlockStoreCSITranslator) CanSupportInline(volume *v1.Volume) bool {
	return volume != nil && volume.AWSElasticBlockStore != nil
}


GetInTreePluginName

// GetInTreePluginName returns the name of the intree plugin driver
func (t *awsElasticBlockStoreCSITranslator) GetInTreePluginName() string {
	return AWSEBSInTreePluginName
}


GetCSIPluginName

// GetCSIPluginName returns the name of the CSI plugin
func (t *awsElasticBlockStoreCSITranslator) GetCSIPluginName() string {
	return AWSEBSDriverName
}


RepairVolumeHandle

func (t *awsElasticBlockStoreCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
	return volumeHandle, nil
}


KubernetesVolumeIDToEBSVolumeID

// KubernetesVolumeIDToEBSVolumeID translates Kubernetes volume ID to EBS volume ID
// KubernetesVolumeID forms:
//   - aws://<zone>/<awsVolumeId>
//   - aws:///<awsVolumeId>
//   - <awsVolumeId>
//
// EBS Volume ID form:
//   - vol-<alphanumberic>
//
// This translation shouldn't be needed and should be fixed in long run
// See https://github.com/kubernetes/kubernetes/issues/73730
func KubernetesVolumeIDToEBSVolumeID(kubernetesID string) (string, error) {
	// name looks like aws://availability-zone/awsVolumeId

	// The original idea of the URL-style name was to put the AZ into the
	// host, so we could find the AZ immediately from the name without
	// querying the API.  But it turns out we don't actually need it for
	// multi-AZ clusters, as we put the AZ into the labels on the PV instead.
	// However, if in future we want to support multi-AZ cluster
	// volume-awareness without using PersistentVolumes, we likely will
	// want the AZ in the host.
	if !strings.HasPrefix(kubernetesID, "aws://") {
		// Assume a bare aws volume id (vol-1234...)
		return kubernetesID, nil
	}
	url, err := url.Parse(kubernetesID)
	if err != nil {
		// TODO: Maybe we should pass a URL into the Volume functions
		return "", fmt.Errorf("Invalid disk name (%s): %v", kubernetesID, err)
	}
	if url.Scheme != "aws" {
		return "", fmt.Errorf("Invalid scheme for AWS volume (%s)", kubernetesID)
	}

	awsID := url.Path
	awsID = strings.Trim(awsID, "/")

	// We sanity check the resulting volume; the two known formats are
	// vol-12345678 and vol-12345678abcdef01
	if !awsVolumeRegMatch.MatchString(awsID) {
		return "", fmt.Errorf("Invalid format for AWS volume (%s)", kubernetesID)
	}

	return awsID, nil
}


getAwsRegionFromZones

func getAwsRegionFromZones(zones []string) (string, error) {
	regions := sets.String{}
	if len(zones) < 1 {
		return "", fmt.Errorf("no zones specified")
	}

	// AWS zones can be in four forms:
	// us-west-2a, us-gov-east-1a, us-west-2-lax-1a (local zone) and us-east-1-wl1-bos-wlz-1 (wavelength).
	for _, zone := range zones {
		splitZone := strings.Split(zone, "-")
		if (len(splitZone) == 3 || len(splitZone) == 4) && len(splitZone[len(splitZone)-1]) == 2 {
			// this would break if we ever have a location with more than 9 regions, ie us-west-10.
			splitZone[len(splitZone)-1] = splitZone[len(splitZone)-1][:1]
			regions.Insert(strings.Join(splitZone, "-"))
		} else if len(splitZone) == 5 || len(splitZone) == 7 {
			// local zone or wavelength
			regions.Insert(strings.Join(splitZone[:3], "-"))
		} else {
			return "", fmt.Errorf("Unexpected zone format: %v is not a valid AWS zone", zone)
		}
	}
	if regions.Len() != 1 {
		return "", fmt.Errorf("multiple or no regions gotten from zones, got: %v", regions)
	}
	return regions.UnsortedList()[0], nil
}


NewAzureDiskCSITranslator

// NewAzureDiskCSITranslator returns a new instance of azureDiskTranslator
func NewAzureDiskCSITranslator() InTreePlugin {
	return &azureDiskCSITranslator{}
}


TranslateInTreeStorageClassToCSI

// TranslateInTreeStorageClassToCSI translates InTree Azure Disk storage class parameters to CSI storage class
func (t *azureDiskCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
	var (
		generatedTopologies []v1.TopologySelectorTerm
		params              = map[string]string{}
	)
	for k, v := range sc.Parameters {
		switch strings.ToLower(k) {
		case zoneKey:
			generatedTopologies = generateToplogySelectors(AzureDiskTopologyKey, []string{v})
		case zonesKey:
			generatedTopologies = generateToplogySelectors(AzureDiskTopologyKey, strings.Split(v, ","))
		default:
			params[k] = v
		}
	}

	if len(generatedTopologies) > 0 && len(sc.AllowedTopologies) > 0 {
		return nil, fmt.Errorf("cannot simultaneously set allowed topologies and zone/zones parameters")
	} else if len(generatedTopologies) > 0 {
		sc.AllowedTopologies = generatedTopologies
	} else if len(sc.AllowedTopologies) > 0 {
		newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, AzureDiskTopologyKey)
		if err != nil {
			return nil, fmt.Errorf("failed translating allowed topologies: %v", err)
		}
		sc.AllowedTopologies = newTopologies
	}
	sc.AllowedTopologies = t.replaceFailureDomainsToCSI(sc.AllowedTopologies)

	sc.Parameters = params

	return sc, nil
}


TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes a Volume with AzureDisk set from in-tree
// and converts the AzureDisk source to a CSIPersistentVolumeSource
func (t *azureDiskCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil || volume.AzureDisk == nil {
		return nil, fmt.Errorf("volume is nil or Azure Disk not defined on volume")
	}

	azureSource := volume.AzureDisk
	if azureSource.Kind != nil && !strings.EqualFold(string(*azureSource.Kind), managed) {
		return nil, fmt.Errorf("kind(%v) is not supported in csi migration", *azureSource.Kind)
	}
	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			// Must be unique per disk as it is used as the unique part of the
			// staging path
			Name: azureSource.DataDiskURI,
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeSource: v1.PersistentVolumeSource{
				CSI: &v1.CSIPersistentVolumeSource{
					Driver:           AzureDiskDriverName,
					VolumeHandle:     azureSource.DataDiskURI,
					VolumeAttributes: map[string]string{azureDiskKind: managed},
				},
			},
			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
		},
	}
	if azureSource.ReadOnly != nil {
		pv.Spec.PersistentVolumeSource.CSI.ReadOnly = *azureSource.ReadOnly
	}

	if azureSource.CachingMode != nil && *azureSource.CachingMode != "" {
		pv.Spec.PersistentVolumeSource.CSI.VolumeAttributes[azureDiskCachingMode] = string(*azureSource.CachingMode)
	}
	if azureSource.FSType != nil {
		pv.Spec.PersistentVolumeSource.CSI.FSType = *azureSource.FSType
		pv.Spec.PersistentVolumeSource.CSI.VolumeAttributes[azureDiskFSType] = *azureSource.FSType
	}
	pv.Spec.PersistentVolumeSource.CSI.VolumeAttributes[azureDiskKind] = managed

	return pv, nil
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a PV with AzureDisk set from in-tree
// and converts the AzureDisk source to a CSIPersistentVolumeSource
func (t *azureDiskCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.AzureDisk == nil {
		return nil, fmt.Errorf("pv is nil or Azure Disk source not defined on pv")
	}

	var (
		azureSource = pv.Spec.PersistentVolumeSource.AzureDisk

		// refer to https://github.com/kubernetes-sigs/azuredisk-csi-driver/blob/master/docs/driver-parameters.md
		csiSource = &v1.CSIPersistentVolumeSource{
			Driver:           AzureDiskDriverName,
			VolumeAttributes: map[string]string{azureDiskKind: managed},
			VolumeHandle:     azureSource.DataDiskURI,
		}
	)

	if azureSource.Kind != nil && !strings.EqualFold(string(*azureSource.Kind), managed) {
		return nil, fmt.Errorf("kind(%v) is not supported in csi migration", *azureSource.Kind)
	}

	if azureSource.CachingMode != nil {
		csiSource.VolumeAttributes[azureDiskCachingMode] = string(*azureSource.CachingMode)
	}

	if azureSource.FSType != nil {
		csiSource.FSType = *azureSource.FSType
		csiSource.VolumeAttributes[azureDiskFSType] = *azureSource.FSType
	}
	csiSource.VolumeAttributes[azureDiskKind] = managed

	if azureSource.ReadOnly != nil {
		csiSource.ReadOnly = *azureSource.ReadOnly
	}

	pv.Spec.PersistentVolumeSource.AzureDisk = nil
	pv.Spec.PersistentVolumeSource.CSI = csiSource

	return pv, nil
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and
// translates the Azure Disk CSI source to a AzureDisk source.
func (t *azureDiskCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
	}
	csiSource := pv.Spec.CSI

	diskURI := csiSource.VolumeHandle
	diskName, err := getDiskName(diskURI)
	if err != nil {
		return nil, err
	}

	// refer to https://github.com/kubernetes-sigs/azuredisk-csi-driver/blob/master/docs/driver-parameters.md
	managed := v1.AzureManagedDisk
	azureSource := &v1.AzureDiskVolumeSource{
		DiskName:    diskName,
		DataDiskURI: diskURI,
		FSType:      &csiSource.FSType,
		ReadOnly:    &csiSource.ReadOnly,
		Kind:        &managed,
	}

	if csiSource.VolumeAttributes != nil {
		for k, v := range csiSource.VolumeAttributes {
			switch strings.ToLower(k) {
			case azureDiskCachingMode:
				if v != "" {
					mode := v1.AzureDataDiskCachingMode(v)
					azureSource.CachingMode = &mode
				}
			case azureDiskFSType:
				if v != "" {
					fsType := v
					azureSource.FSType = &fsType
				}
			}
		}
		azureSource.Kind = &managed
	}

	pv.Spec.CSI = nil
	pv.Spec.AzureDisk = azureSource

	return pv, nil
}


CanSupport

// CanSupport tests whether the plugin supports a given volume
// specification from the API.  The spec pointer should be considered
// const.
func (t *azureDiskCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
	return pv != nil && pv.Spec.AzureDisk != nil
}


CanSupportInline

// CanSupportInline tests whether the plugin supports a given inline volume
// specification from the API.  The spec pointer should be considered
// const.
func (t *azureDiskCSITranslator) CanSupportInline(volume *v1.Volume) bool {
	return volume != nil && volume.AzureDisk != nil
}


GetInTreePluginName

// GetInTreePluginName returns the name of the intree plugin driver
func (t *azureDiskCSITranslator) GetInTreePluginName() string {
	return AzureDiskInTreePluginName
}


GetCSIPluginName

// GetCSIPluginName returns the name of the CSI plugin
func (t *azureDiskCSITranslator) GetCSIPluginName() string {
	return AzureDiskDriverName
}


RepairVolumeHandle

func (t *azureDiskCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
	return volumeHandle, nil
}


isManagedDisk

func isManagedDisk(diskURI string) bool {
	if len(diskURI) > 4 && strings.ToLower(diskURI[:4]) == "http" {
		return false
	}
	return true
}


getDiskName

func getDiskName(diskURI string) (string, error) {
	diskPathRE := managedDiskPathRE
	if !isManagedDisk(diskURI) {
		diskPathRE = unmanagedDiskPathRE
	}

	matches := diskPathRE.FindStringSubmatch(diskURI)
	if len(matches) != 2 {
		return "", fmt.Errorf("could not get disk name from %s, correct format: %s", diskURI, diskPathRE)
	}
	return matches[1], nil
}


replaceFailureDomainsToCSI

// Replace topology values for failure domains ("<number>") to "",
// as it's the value that the CSI driver expects.
func (t *azureDiskCSITranslator) replaceFailureDomainsToCSI(terms []v1.TopologySelectorTerm) []v1.TopologySelectorTerm {
	if terms == nil {
		return nil
	}

	newTopologies := []v1.TopologySelectorTerm{}
	for _, term := range terms {
		newTerm := term.DeepCopy()
		for i := range newTerm.MatchLabelExpressions {
			exp := &newTerm.MatchLabelExpressions[i]
			if exp.Key == AzureDiskTopologyKey {
				for j := range exp.Values {
					if unzonedCSIRegionRE.Match([]byte(exp.Values[j])) {
						// Topologies "0", "1" etc are used when in-tree topology is translated to CSI in Azure
						// regions that don't have availability zones. E.g.:
						//    topology.kubernetes.io/region: westus
						//    topology.kubernetes.io/zone: "0"
						// The CSI driver uses zone "" instead of "0" in this case.
						//    topology.disk.csi.azure.com/zone": ""
						exp.Values[j] = ""
					}
				}
			}
		}
		newTopologies = append(newTopologies, *newTerm)
	}
	return newTopologies
}


NewAzureFileCSITranslator

// NewAzureFileCSITranslator returns a new instance of azureFileTranslator
func NewAzureFileCSITranslator() InTreePlugin {
	return &azureFileCSITranslator{}
}


TranslateInTreeStorageClassToCSI

// TranslateInTreeStorageClassToCSI translates InTree Azure File storage class parameters to CSI storage class
func (t *azureFileCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
	return sc, nil
}


TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes a Volume with AzureFile set from in-tree
// and converts the AzureFile source to a CSIPersistentVolumeSource
func (t *azureFileCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil || volume.AzureFile == nil {
		return nil, fmt.Errorf("volume is nil or Azure File not defined on volume")
	}

	azureSource := volume.AzureFile
	accountName, err := getStorageAccountName(azureSource.SecretName)
	if err != nil {
		klog.Warningf("getStorageAccountName(%s) returned with error: %v", azureSource.SecretName, err)
		accountName = azureSource.SecretName
	}

	secretNamespace := defaultSecretNamespace
	if podNamespace != "" {
		secretNamespace = podNamespace
	}
	volumeID := fmt.Sprintf(volumeIDTemplate, "", accountName, azureSource.ShareName, volume.Name, secretNamespace)

	var (
		pv = &v1.PersistentVolume{
			ObjectMeta: metav1.ObjectMeta{
				// Must be unique as it is used as the unique part of the staging path
				Name: volumeID,
			},
			Spec: v1.PersistentVolumeSpec{
				PersistentVolumeSource: v1.PersistentVolumeSource{
					CSI: &v1.CSIPersistentVolumeSource{
						Driver:           AzureFileDriverName,
						VolumeHandle:     volumeID,
						ReadOnly:         azureSource.ReadOnly,
						VolumeAttributes: map[string]string{shareNameField: azureSource.ShareName},
						NodeStageSecretRef: &v1.SecretReference{
							Name:      azureSource.SecretName,
							Namespace: secretNamespace,
						},
					},
				},
				AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany},
			},
		}
	)

	return pv, nil
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a PV with AzureFile set from in-tree
// and converts the AzureFile source to a CSIPersistentVolumeSource
func (t *azureFileCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.AzureFile == nil {
		return nil, fmt.Errorf("pv is nil or Azure File source not defined on pv")
	}

	azureSource := pv.Spec.PersistentVolumeSource.AzureFile
	accountName, err := getStorageAccountName(azureSource.SecretName)
	if err != nil {
		klog.Warningf("getStorageAccountName(%s) returned with error: %v", azureSource.SecretName, err)
		accountName = azureSource.SecretName
	}
	resourceGroup := ""
	if pv.ObjectMeta.Annotations != nil {
		if v, ok := pv.ObjectMeta.Annotations[resourceGroupAnnotation]; ok {
			resourceGroup = v
		}
	}

	// Secret is required when mounting a volume but pod presence cannot be assumed - we should not try to read pod now.
	namespace := ""
	// Try to read SecretNamespace from source pv.
	if azureSource.SecretNamespace != nil {
		namespace = *azureSource.SecretNamespace
	} else {
		// Try to read namespace from ClaimRef which should be always present.
		if pv.Spec.ClaimRef != nil {
			namespace = pv.Spec.ClaimRef.Namespace
		}
	}

	if len(namespace) == 0 {
		return nil, fmt.Errorf("could not find a secret namespace in PersistentVolumeSource or ClaimRef")
	}

	volumeID := fmt.Sprintf(volumeIDTemplate, resourceGroup, accountName, azureSource.ShareName, pv.ObjectMeta.Name, namespace)

	var (
		// refer to https://github.com/kubernetes-sigs/azurefile-csi-driver/blob/master/docs/driver-parameters.md
		csiSource = &v1.CSIPersistentVolumeSource{
			Driver: AzureFileDriverName,
			NodeStageSecretRef: &v1.SecretReference{
				Name:      azureSource.SecretName,
				Namespace: namespace,
			},
			ReadOnly:         azureSource.ReadOnly,
			VolumeAttributes: map[string]string{shareNameField: azureSource.ShareName},
			VolumeHandle:     volumeID,
		}
	)

	pv.Spec.PersistentVolumeSource.AzureFile = nil
	pv.Spec.PersistentVolumeSource.CSI = csiSource

	return pv, nil
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and
// translates the Azure File CSI source to a AzureFile source.
func (t *azureFileCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
	}
	csiSource := pv.Spec.CSI

	// refer to https://github.com/kubernetes-sigs/azurefile-csi-driver/blob/master/docs/driver-parameters.md
	azureSource := &v1.AzureFilePersistentVolumeSource{
		ReadOnly: csiSource.ReadOnly,
	}

	for k, v := range csiSource.VolumeAttributes {
		switch strings.ToLower(k) {
		case shareNameField:
			azureSource.ShareName = v
		case secretNameField:
			azureSource.SecretName = v
		case secretNamespaceField:
			ns := v
			azureSource.SecretNamespace = &ns
		}
	}

	resourceGroup := ""
	if csiSource.NodeStageSecretRef != nil && csiSource.NodeStageSecretRef.Name != "" {
		azureSource.SecretName = csiSource.NodeStageSecretRef.Name
		azureSource.SecretNamespace = &csiSource.NodeStageSecretRef.Namespace
	}
	if azureSource.ShareName == "" || azureSource.SecretName == "" {
		rg, storageAccount, fileShareName, _, err := getFileShareInfo(csiSource.VolumeHandle)
		if err != nil {
			return nil, err
		}
		if azureSource.ShareName == "" {
			azureSource.ShareName = fileShareName
		}
		if azureSource.SecretName == "" {
			azureSource.SecretName = fmt.Sprintf(secretNameTemplate, storageAccount)
		}
		resourceGroup = rg
	}

	if azureSource.SecretNamespace == nil {
		ns := defaultSecretNamespace
		azureSource.SecretNamespace = &ns
	}

	pv.Spec.CSI = nil
	pv.Spec.AzureFile = azureSource
	if pv.ObjectMeta.Annotations == nil {
		pv.ObjectMeta.Annotations = map[string]string{}
	}
	if resourceGroup != "" {
		pv.ObjectMeta.Annotations[resourceGroupAnnotation] = resourceGroup
	}

	return pv, nil
}


CanSupport

// CanSupport tests whether the plugin supports a given volume
// specification from the API.  The spec pointer should be considered
// const.
func (t *azureFileCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
	return pv != nil && pv.Spec.AzureFile != nil
}


CanSupportInline

// CanSupportInline tests whether the plugin supports a given inline volume
// specification from the API.  The spec pointer should be considered
// const.
func (t *azureFileCSITranslator) CanSupportInline(volume *v1.Volume) bool {
	return volume != nil && volume.AzureFile != nil
}


GetInTreePluginName

// GetInTreePluginName returns the name of the intree plugin driver
func (t *azureFileCSITranslator) GetInTreePluginName() string {
	return AzureFileInTreePluginName
}


GetCSIPluginName

// GetCSIPluginName returns the name of the CSI plugin
func (t *azureFileCSITranslator) GetCSIPluginName() string {
	return AzureFileDriverName
}


RepairVolumeHandle

func (t *azureFileCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
	return volumeHandle, nil
}


getFileShareInfo

// get file share info according to volume id, e.g.
// input: "rg#f5713de20cde511e8ba4900#pvc-file-dynamic-17e43f84-f474-11e8-acd0-000d3a00df41#diskname.vhd"
// output: rg, f5713de20cde511e8ba4900, pvc-file-dynamic-17e43f84-f474-11e8-acd0-000d3a00df41, diskname.vhd
func getFileShareInfo(id string) (string, string, string, string, error) {
	segments := strings.Split(id, separator)
	if len(segments) < 3 {
		return "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id)
	}
	var diskName string
	if len(segments) > 3 {
		diskName = segments[3]
	}
	return segments[0], segments[1], segments[2], diskName, nil
}


getStorageAccountName

// get storage account name from secret name
func getStorageAccountName(secretName string) (string, error) {
	matches := secretNameFormatRE.FindStringSubmatch(secretName)
	if len(matches) != 2 {
		return "", fmt.Errorf("could not get account name from %s, correct format: %s", secretName, secretNameFormatRE)
	}
	return matches[1], nil
}


NewGCEPersistentDiskCSITranslator

// NewGCEPersistentDiskCSITranslator returns a new instance of gcePersistentDiskTranslator
func NewGCEPersistentDiskCSITranslator() InTreePlugin {
	return &gcePersistentDiskCSITranslator{}
}


generateToplogySelectors

func generateToplogySelectors(key string, values []string) []v1.TopologySelectorTerm {
	return []v1.TopologySelectorTerm{
		{
			MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
				{
					Key:    key,
					Values: values,
				},
			},
		},
	}
}


TranslateInTreeStorageClassToCSI

// TranslateInTreeStorageClassToCSI translates InTree GCE storage class parameters to CSI storage class
func (g *gcePersistentDiskCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
	var generatedTopologies []v1.TopologySelectorTerm

	np := map[string]string{}
	for k, v := range sc.Parameters {
		switch strings.ToLower(k) {
		case fsTypeKey:
			// prefixed fstype parameter is stripped out by external provisioner
			np[csiFsTypeKey] = v
		// Strip out zone and zones parameters and translate them into topologies instead
		case zoneKey:
			generatedTopologies = generateToplogySelectors(GCEPDTopologyKey, []string{v})
		case zonesKey:
			generatedTopologies = generateToplogySelectors(GCEPDTopologyKey, strings.Split(v, ","))
		default:
			np[k] = v
		}
	}

	if len(generatedTopologies) > 0 && len(sc.AllowedTopologies) > 0 {
		return nil, fmt.Errorf("cannot simultaneously set allowed topologies and zone/zones parameters")
	} else if len(generatedTopologies) > 0 {
		sc.AllowedTopologies = generatedTopologies
	} else if len(sc.AllowedTopologies) > 0 {
		newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, GCEPDTopologyKey)
		if err != nil {
			return nil, fmt.Errorf("failed translating allowed topologies: %v", err)
		}
		sc.AllowedTopologies = newTopologies
	}

	sc.Parameters = np

	return sc, nil
}


backwardCompatibleAccessModes

// backwardCompatibleAccessModes translates all instances of ReadWriteMany
// access mode from the in-tree plugin to ReadWriteOnce. This is because in-tree
// plugin never supported ReadWriteMany but also did not validate or enforce
// this access mode for pre-provisioned volumes. The GCE PD CSI Driver validates
// and enforces (fails) ReadWriteMany. Therefore we treat all in-tree
// ReadWriteMany as ReadWriteOnce volumes to not break legacy volumes. It also
// takes [ReadWriteOnce, ReadOnlyMany] and makes it ReadWriteOnce. This is
// because the in-tree plugin does not enforce access modes and just attaches
// the disk in ReadWriteOnce mode; however, the CSI external-attacher will fail
// this combination because technically [ReadWriteOnce, ReadOnlyMany] is not
// supportable on an attached volume
// See: https://github.com/kubernetes-csi/external-attacher/issues/153
func backwardCompatibleAccessModes(ams []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
	if ams == nil {
		return nil
	}

	s := map[v1.PersistentVolumeAccessMode]bool{}
	var newAM []v1.PersistentVolumeAccessMode

	for _, am := range ams {
		if am == v1.ReadWriteMany {
			// ReadWriteMany is unsupported in CSI, but in-tree did no
			// validation and treated it as ReadWriteOnce
			s[v1.ReadWriteOnce] = true
		} else {
			s[am] = true
		}
	}

	switch {
	case s[v1.ReadOnlyMany] && s[v1.ReadWriteOnce]:
		// ROX,RWO is unsupported in CSI, but in-tree did not validation and
		// treated it as ReadWriteOnce
		newAM = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
	case s[v1.ReadWriteOnce]:
		newAM = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
	case s[v1.ReadOnlyMany]:
		newAM = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
	default:
		newAM = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
	}

	return newAM
}


TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes a Volume with GCEPersistentDisk set from in-tree
// and converts the GCEPersistentDisk source to a CSIPersistentVolumeSource
func (g *gcePersistentDiskCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil || volume.GCEPersistentDisk == nil {
		return nil, fmt.Errorf("volume is nil or GCE PD not defined on volume")
	}

	pdSource := volume.GCEPersistentDisk

	partition := ""
	if pdSource.Partition != 0 {
		partition = strconv.Itoa(int(pdSource.Partition))
	}

	var am v1.PersistentVolumeAccessMode
	if pdSource.ReadOnly {
		am = v1.ReadOnlyMany
	} else {
		am = v1.ReadWriteOnce
	}

	fsMode := v1.PersistentVolumeFilesystem
	return &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			// Must be unique per disk as it is used as the unique part of the
			// staging path
			Name: fmt.Sprintf("%s-%s", GCEPDDriverName, pdSource.PDName),
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeSource: v1.PersistentVolumeSource{
				CSI: &v1.CSIPersistentVolumeSource{
					Driver:       GCEPDDriverName,
					VolumeHandle: fmt.Sprintf(volIDZonalFmt, UnspecifiedValue, UnspecifiedValue, pdSource.PDName),
					ReadOnly:     pdSource.ReadOnly,
					FSType:       pdSource.FSType,
					VolumeAttributes: map[string]string{
						"partition": partition,
					},
				},
			},
			AccessModes: []v1.PersistentVolumeAccessMode{am},
			VolumeMode:  &fsMode,
		},
	}, nil
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a PV with GCEPersistentDisk set from in-tree
// and converts the GCEPersistentDisk source to a CSIPersistentVolumeSource
func (g *gcePersistentDiskCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	var volID string

	if pv == nil || pv.Spec.GCEPersistentDisk == nil {
		return nil, fmt.Errorf("pv is nil or GCE Persistent Disk source not defined on pv")
	}

	// depend on which version it migrates from, the label could be failuredomain beta or topology GA version
	zonesLabel := pv.Labels[v1.LabelFailureDomainBetaZone]
	if zonesLabel == "" {
		zonesLabel = pv.Labels[v1.LabelTopologyZone]
	}

	zones := strings.Split(zonesLabel, labelMultiZoneDelimiter)
	if len(zones) == 1 && len(zones[0]) != 0 {
		// Zonal
		volID = fmt.Sprintf(volIDZonalFmt, UnspecifiedValue, zones[0], pv.Spec.GCEPersistentDisk.PDName)
	} else if len(zones) > 1 {
		// Regional
		region, err := gceGetRegionFromZones(zones)
		if err != nil {
			return nil, fmt.Errorf("failed to get region from zones: %v", err)
		}
		volID = fmt.Sprintf(volIDRegionalFmt, UnspecifiedValue, region, pv.Spec.GCEPersistentDisk.PDName)
	} else {
		// Unspecified
		volID = fmt.Sprintf(volIDZonalFmt, UnspecifiedValue, UnspecifiedValue, pv.Spec.GCEPersistentDisk.PDName)
	}

	gceSource := pv.Spec.PersistentVolumeSource.GCEPersistentDisk

	partition := ""
	if gceSource.Partition != 0 {
		partition = strconv.Itoa(int(gceSource.Partition))
	}

	csiSource := &v1.CSIPersistentVolumeSource{
		Driver:       GCEPDDriverName,
		VolumeHandle: volID,
		ReadOnly:     gceSource.ReadOnly,
		FSType:       gceSource.FSType,
		VolumeAttributes: map[string]string{
			"partition": partition,
		},
	}

	if err := translateTopologyFromInTreeToCSI(pv, GCEPDTopologyKey); err != nil {
		return nil, fmt.Errorf("failed to translate topology: %v", err)
	}

	pv.Spec.PersistentVolumeSource.GCEPersistentDisk = nil
	pv.Spec.PersistentVolumeSource.CSI = csiSource
	pv.Spec.AccessModes = backwardCompatibleAccessModes(pv.Spec.AccessModes)

	return pv, nil
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and
// translates the GCE PD CSI source to a GCEPersistentDisk source.
func (g *gcePersistentDiskCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
	}
	csiSource := pv.Spec.CSI

	pdName, err := pdNameFromVolumeID(csiSource.VolumeHandle)
	if err != nil {
		return nil, err
	}

	gceSource := &v1.GCEPersistentDiskVolumeSource{
		PDName:   pdName,
		FSType:   csiSource.FSType,
		ReadOnly: csiSource.ReadOnly,
	}
	if partition, ok := csiSource.VolumeAttributes["partition"]; ok && partition != "" {
		partInt, err := strconv.Atoi(partition)
		if err != nil {
			return nil, fmt.Errorf("Failed to convert partition %v to integer: %v", partition, err)
		}
		gceSource.Partition = int32(partInt)
	}

	// translate CSI topology to In-tree topology for rollback compatibility
	if err := translateTopologyFromCSIToInTree(pv, GCEPDTopologyKey, gceGetRegionFromZones); err != nil {
		return nil, fmt.Errorf("failed to translate topology. PV:%+v. Error:%v", *pv, err)
	}

	pv.Spec.CSI = nil
	pv.Spec.GCEPersistentDisk = gceSource

	return pv, nil
}


CanSupport

// CanSupport tests whether the plugin supports a given persistent volume
// specification from the API.  The spec pointer should be considered
// const.
func (g *gcePersistentDiskCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
	return pv != nil && pv.Spec.GCEPersistentDisk != nil
}


CanSupportInline

// CanSupportInline tests whether the plugin supports a given inline volume
// specification from the API.  The spec pointer should be considered
// const.
func (g *gcePersistentDiskCSITranslator) CanSupportInline(volume *v1.Volume) bool {
	return volume != nil && volume.GCEPersistentDisk != nil
}


GetInTreePluginName

// GetInTreePluginName returns the name of the intree plugin driver
func (g *gcePersistentDiskCSITranslator) GetInTreePluginName() string {
	return GCEPDInTreePluginName
}


GetCSIPluginName

// GetCSIPluginName returns the name of the CSI plugin
func (g *gcePersistentDiskCSITranslator) GetCSIPluginName() string {
	return GCEPDDriverName
}


RepairVolumeHandle

// RepairVolumeHandle returns a fully specified volume handle by inferring
// project, zone/region from the node ID if the volume handle has UNSPECIFIED
// sections
func (g *gcePersistentDiskCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
	var err error
	tok := strings.Split(volumeHandle, "/")
	if len(tok) < volIDTotalElements {
		return "", fmt.Errorf("volume handle has wrong number of elements; got %v, wanted %v or more", len(tok), volIDTotalElements)
	}
	if tok[volIDProjectValue] != UnspecifiedValue {
		return volumeHandle, nil
	}

	nodeTok := strings.Split(nodeID, "/")
	if len(nodeTok) < volIDTotalElements {
		return "", fmt.Errorf("node handle has wrong number of elements; got %v, wanted %v or more", len(nodeTok), volIDTotalElements)
	}

	switch tok[volIDRegionalityValue] {
	case "zones":
		zone := ""
		if tok[volIDZoneValue] == UnspecifiedValue {
			zone = nodeTok[volIDZoneValue]
		} else {
			zone = tok[volIDZoneValue]
		}
		return fmt.Sprintf(volIDZonalFmt, nodeTok[volIDProjectValue], zone, tok[volIDDiskNameValue]), nil
	case "regions":
		region := ""
		if tok[volIDZoneValue] == UnspecifiedValue {
			region, err = gceGetRegionFromZones([]string{nodeTok[volIDZoneValue]})
			if err != nil {
				return "", fmt.Errorf("failed to get region from zone %s: %v", nodeTok[volIDZoneValue], err)
			}
		} else {
			region = tok[volIDZoneValue]
		}
		return fmt.Sprintf(volIDRegionalFmt, nodeTok[volIDProjectValue], region, tok[volIDDiskNameValue]), nil
	default:
		return "", fmt.Errorf("expected volume handle to have zones or regions regionality value, got: %s", tok[volIDRegionalityValue])
	}
}


pdNameFromVolumeID

func pdNameFromVolumeID(id string) (string, error) {
	splitID := strings.Split(id, "/")
	if len(splitID) < volIDTotalElements {
		return "", fmt.Errorf("failed to get id components.Got: %v, wanted %v components or more. ", len(splitID), volIDTotalElements)
	}
	return splitID[volIDDiskNameValue], nil
}


gceGetRegionFromZones

// TODO: Replace this with the imported one from GCE PD CSI Driver when
// the driver removes all k8s/k8s dependencies
func gceGetRegionFromZones(zones []string) (string, error) {
	regions := sets.String{}
	if len(zones) < 1 {
		return "", fmt.Errorf("no zones specified")
	}
	for _, zone := range zones {
		// Zone expected format {locale}-{region}-{zone}
		splitZone := strings.Split(zone, "-")
		if len(splitZone) != 3 {
			return "", fmt.Errorf("zone in unexpected format, expected: {locale}-{region}-{zone}, got: %v", zone)
		}
		regions.Insert(strings.Join(splitZone[0:2], "-"))
	}
	if regions.Len() != 1 {
		return "", fmt.Errorf("multiple or no regions gotten from zones, got: %v", regions)
	}
	return regions.UnsortedList()[0], nil
}


replaceTopology

// replaceTopology overwrites an existing key in NodeAffinity by a new one.
// If there are any newKey already exist in an expression of a term, we will
// not combine the replaced key Values with the existing ones.
// So there might be duplication if there is any newKey expression
// already in the terms.
func replaceTopology(pv *v1.PersistentVolume, oldKey, newKey string) error {
	// Make sure the necessary fields exist
	if pv == nil || pv.Spec.NodeAffinity == nil || pv.Spec.NodeAffinity.Required == nil ||
		pv.Spec.NodeAffinity.Required.NodeSelectorTerms == nil || len(pv.Spec.NodeAffinity.Required.NodeSelectorTerms) == 0 {
		return nil
	}
	for i := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms {
		for j, r := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions {
			if r.Key == oldKey {
				pv.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions[j].Key = newKey
			}
		}
	}

	return nil
}


getTopologyValues

// getTopologyValues returns all unique topology values with the given key found in
// the PV NodeAffinity. Sort by alphabetical order.
// This function collapses multiple zones into a list that is ORed. This assumes that
// the plugin does not support a constraint like key in "zone1" AND "zone2"
func getTopologyValues(pv *v1.PersistentVolume, key string) []string {
	if pv.Spec.NodeAffinity == nil ||
		pv.Spec.NodeAffinity.Required == nil ||
		len(pv.Spec.NodeAffinity.Required.NodeSelectorTerms) < 1 {
		return nil
	}

	values := make(map[string]bool)
	for i := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms {
		for _, r := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions {
			if r.Key == key {
				for _, v := range r.Values {
					values[v] = true
				}
			}
		}
	}
	// remove duplication and sort them in order for better usage
	var re []string
	for k := range values {
		re = append(re, k)
	}
	sort.Strings(re)
	return re
}


addTopology

// addTopology appends the topology to the given PV to all Terms.
func addTopology(pv *v1.PersistentVolume, topologyKey string, zones []string) error {
	// Make sure there are no duplicate or empty strings
	filteredZones := sets.String{}
	for i := range zones {
		zone := strings.TrimSpace(zones[i])
		if len(zone) > 0 {
			filteredZones.Insert(zone)
		}
	}

	zones = filteredZones.List()
	if len(zones) < 1 {
		return errors.New("there are no valid zones to add to pv")
	}

	// Make sure the necessary fields exist
	if pv.Spec.NodeAffinity == nil {
		pv.Spec.NodeAffinity = new(v1.VolumeNodeAffinity)
	}

	if pv.Spec.NodeAffinity.Required == nil {
		pv.Spec.NodeAffinity.Required = new(v1.NodeSelector)
	}

	if len(pv.Spec.NodeAffinity.Required.NodeSelectorTerms) == 0 {
		pv.Spec.NodeAffinity.Required.NodeSelectorTerms = make([]v1.NodeSelectorTerm, 1)
	}

	topology := v1.NodeSelectorRequirement{
		Key:      topologyKey,
		Operator: v1.NodeSelectorOpIn,
		Values:   zones,
	}

	// add the CSI topology to each term
	for i := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms {
		pv.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions = append(
			pv.Spec.NodeAffinity.Required.NodeSelectorTerms[i].MatchExpressions,
			topology,
		)
	}

	return nil
}


translateTopologyFromInTreeToCSI

// translateTopologyFromInTreeToCSI converts existing zone labels or in-tree topology to CSI topology.
// In-tree topology has precedence over zone labels. When both in-tree topology and zone labels exist
// for a particular CSI topology, in-tree topology will be used.
// This function will remove the Beta version Kubernetes topology label in case the node upgrade to a
// newer version where it does not have any Beta topology label anymore
func translateTopologyFromInTreeToCSI(pv *v1.PersistentVolume, csiTopologyKey string) error {

	zoneLabel, regionLabel := getTopologyLabel(pv)

	// If Zone kubernetes topology exist, replace it to use csiTopologyKey
	zones := getTopologyValues(pv, zoneLabel)
	if len(zones) > 0 {
		replaceTopology(pv, zoneLabel, csiTopologyKey)
	} else {
		// if nothing is in the NodeAffinity, try to fetch the topology from PV labels
		if label, ok := pv.Labels[zoneLabel]; ok {
			zones = strings.Split(label, labelMultiZoneDelimiter)
			if len(zones) > 0 {
				addTopology(pv, csiTopologyKey, zones)
			}
		}
	}

	// if the in-tree PV has beta region label, replace it with GA label to ensure
	// the scheduler is able to schedule it on new nodes with only GA kubernetes label
	// No need to check it for zone label because it has already been replaced if exist
	if regionLabel == v1.LabelFailureDomainBetaRegion {
		regions := getTopologyValues(pv, regionLabel)
		if len(regions) > 0 {
			replaceTopology(pv, regionLabel, v1.LabelTopologyRegion)
		}
	}

	return nil
}


getTopologyLabel

// getTopologyLabel checks if the kubernetes topology label used in this
// PV is GA and return the zone/region label used.
// The version checking follows the following orders
//  1. Check NodeAffinity
//     1.1 Check if zoneGA exists, if yes return GA labels
//     1.2 Check if zoneBeta exists, if yes return Beta labels
//  2. Check PV labels
//     2.1 Check if zoneGA exists, if yes return GA labels
//     2.2 Check if zoneBeta exists, if yes return Beta labels
func getTopologyLabel(pv *v1.PersistentVolume) (zoneLabel string, regionLabel string) {

	if zoneGA := TopologyKeyExist(v1.LabelTopologyZone, pv.Spec.NodeAffinity); zoneGA {
		return v1.LabelTopologyZone, v1.LabelTopologyRegion
	}
	if zoneBeta := TopologyKeyExist(v1.LabelFailureDomainBetaZone, pv.Spec.NodeAffinity); zoneBeta {
		return v1.LabelFailureDomainBetaZone, v1.LabelFailureDomainBetaRegion
	}
	if _, zoneGA := pv.Labels[v1.LabelTopologyZone]; zoneGA {
		return v1.LabelTopologyZone, v1.LabelTopologyRegion
	}
	if _, zoneBeta := pv.Labels[v1.LabelFailureDomainBetaZone]; zoneBeta {
		return v1.LabelFailureDomainBetaZone, v1.LabelFailureDomainBetaRegion
	}
	// No labels or NodeAffinity exist, default to GA version
	return v1.LabelTopologyZone, v1.LabelTopologyRegion
}


TopologyKeyExist

// TopologyKeyExist checks if a certain key exists in a VolumeNodeAffinity
func TopologyKeyExist(key string, vna *v1.VolumeNodeAffinity) bool {
	if vna == nil || vna.Required == nil || vna.Required.NodeSelectorTerms == nil || len(vna.Required.NodeSelectorTerms) == 0 {
		return false
	}

	for _, nodeSelectorTerms := range vna.Required.NodeSelectorTerms {
		nsrequirements := nodeSelectorTerms.MatchExpressions
		for _, nodeSelectorRequirement := range nsrequirements {
			if nodeSelectorRequirement.Key == key {
				return true
			}
		}
	}
	return false
}


translateTopologyFromCSIToInTree

// translateTopologyFromCSIToInTree translate a CSI topology to
// Kubernetes topology and add topology labels to it. Note that this function
// will only work for plugin with a single topologyKey that translates to
// Kubernetes zone(and region if regionParser is passed in).
// If a plugin has more than one topologyKey, it will need to be processed
// separately by the plugin.
// If regionParser is nil, no region NodeAffinity will be added. If not nil,
// it'll be passed to regionTopologyHandler, which will add region topology NodeAffinity
// and labels for the given PV. It assumes the Zone NodeAffinity already exists.
// In short this function will,
// 1. Replace all CSI topology to Kubernetes Zone topology label
// 2. Process and generate region topology if a regionParser is passed
// 3. Add Kubernetes Topology labels(zone) if they do not exist
func translateTopologyFromCSIToInTree(pv *v1.PersistentVolume, csiTopologyKey string, regionParser regionParserFn) error {

	zoneLabel, _ := getTopologyLabel(pv)

	// 1. Replace all CSI topology to Kubernetes Zone label
	err := replaceTopology(pv, csiTopologyKey, zoneLabel)
	if err != nil {
		return fmt.Errorf("Failed to replace CSI topology to Kubernetes topology, error: %v", err)
	}

	// 2. Take care of region topology if a regionParser is passed
	if regionParser != nil {
		// let's make less strict on this one. Even if there is an error in the region processing, just ignore it
		err = regionTopologyHandler(pv, regionParser)
		if err != nil {
			return fmt.Errorf("Failed to handle region topology. error: %v", err)
		}
	}

	// 3. Add labels about Kubernetes Topology
	zoneVals := getTopologyValues(pv, zoneLabel)
	if len(zoneVals) > 0 {
		if pv.Labels == nil {
			pv.Labels = make(map[string]string)
		}
		_, zoneOK := pv.Labels[zoneLabel]
		if !zoneOK {
			zoneValStr := strings.Join(zoneVals, labelMultiZoneDelimiter)
			pv.Labels[zoneLabel] = zoneValStr
		}
	}

	return nil
}


translateAllowedTopologies

// translateAllowedTopologies translates allowed topologies within storage class or PV
// from legacy failure domain to given CSI topology key
func translateAllowedTopologies(terms []v1.TopologySelectorTerm, key string) ([]v1.TopologySelectorTerm, error) {
	if terms == nil {
		return nil, nil
	}

	newTopologies := []v1.TopologySelectorTerm{}
	for _, term := range terms {
		newTerm := v1.TopologySelectorTerm{}
		for _, exp := range term.MatchLabelExpressions {
			var newExp v1.TopologySelectorLabelRequirement
			if exp.Key == v1.LabelFailureDomainBetaZone || exp.Key == v1.LabelTopologyZone {
				newExp = v1.TopologySelectorLabelRequirement{
					Key:    key,
					Values: exp.Values,
				}
			} else {
				// Other topologies are passed through unchanged.
				newExp = exp
			}
			newTerm.MatchLabelExpressions = append(newTerm.MatchLabelExpressions, newExp)
		}
		newTopologies = append(newTopologies, newTerm)
	}
	return newTopologies, nil
}


regionTopologyHandler

// regionTopologyHandler will process the PV and add region
// kubernetes topology label to its NodeAffinity and labels
// It assumes the Zone NodeAffinity already exists
// Each provider is responsible for providing their own regionParser
func regionTopologyHandler(pv *v1.PersistentVolume, regionParser regionParserFn) error {

	// Make sure the necessary fields exist
	if pv == nil || pv.Spec.NodeAffinity == nil || pv.Spec.NodeAffinity.Required == nil ||
		pv.Spec.NodeAffinity.Required.NodeSelectorTerms == nil || len(pv.Spec.NodeAffinity.Required.NodeSelectorTerms) == 0 {
		return nil
	}

	zoneLabel, regionLabel := getTopologyLabel(pv)

	// process each term
	for index, nodeSelectorTerm := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms {
		// In the first loop, see if regionLabel already exist
		regionExist := false
		var zoneVals []string
		for _, nsRequirement := range nodeSelectorTerm.MatchExpressions {
			if nsRequirement.Key == regionLabel {
				regionExist = true
				break
			} else if nsRequirement.Key == zoneLabel {
				zoneVals = append(zoneVals, nsRequirement.Values...)
			}
		}
		if regionExist {
			// Regionlabel already exist in this term, skip it
			continue
		}
		// If no regionLabel found, generate region label from the zoneLabel we collect from this term
		regionVal, err := regionParser(zoneVals)
		if err != nil {
			return err
		}
		// Add the regionVal to this term
		pv.Spec.NodeAffinity.Required.NodeSelectorTerms[index].MatchExpressions =
			append(pv.Spec.NodeAffinity.Required.NodeSelectorTerms[index].MatchExpressions, v1.NodeSelectorRequirement{
				Key:      regionLabel,
				Operator: v1.NodeSelectorOpIn,
				Values:   []string{regionVal},
			})

	}

	// Add region label
	regionVals := getTopologyValues(pv, regionLabel)
	if len(regionVals) == 1 {
		// We should only have exactly 1 region value
		if pv.Labels == nil {
			pv.Labels = make(map[string]string)
		}
		_, regionOK := pv.Labels[regionLabel]
		if !regionOK {
			pv.Labels[regionLabel] = regionVals[0]
		}
	}

	return nil
}


NewOpenStackCinderCSITranslator

// NewOpenStackCinderCSITranslator returns a new instance of osCinderCSITranslator
func NewOpenStackCinderCSITranslator() InTreePlugin {
	return &osCinderCSITranslator{}
}


TranslateInTreeStorageClassToCSI

// TranslateInTreeStorageClassToCSI translates InTree Cinder storage class parameters to CSI storage class
func (t *osCinderCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
	var (
		params = map[string]string{}
	)
	for k, v := range sc.Parameters {
		switch strings.ToLower(k) {
		case fsTypeKey:
			params[csiFsTypeKey] = v
		default:
			// All other parameters are supported by the CSI driver.
			// This includes also "availability", therefore do not translate it to sc.AllowedTopologies
			params[k] = v
		}
	}

	if len(sc.AllowedTopologies) > 0 {
		newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, CinderTopologyKey)
		if err != nil {
			return nil, fmt.Errorf("failed translating allowed topologies: %v", err)
		}
		sc.AllowedTopologies = newTopologies
	}

	sc.Parameters = params

	return sc, nil
}


TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes a Volume with Cinder set from in-tree
// and converts the Cinder source to a CSIPersistentVolumeSource
func (t *osCinderCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil || volume.Cinder == nil {
		return nil, fmt.Errorf("volume is nil or Cinder not defined on volume")
	}

	cinderSource := volume.Cinder
	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			// Must be unique per disk as it is used as the unique part of the
			// staging path
			Name: fmt.Sprintf("%s-%s", CinderDriverName, cinderSource.VolumeID),
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeSource: v1.PersistentVolumeSource{
				CSI: &v1.CSIPersistentVolumeSource{
					Driver:           CinderDriverName,
					VolumeHandle:     cinderSource.VolumeID,
					ReadOnly:         cinderSource.ReadOnly,
					FSType:           cinderSource.FSType,
					VolumeAttributes: map[string]string{},
				},
			},
			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
		},
	}
	return pv, nil
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a PV with Cinder set from in-tree
// and converts the Cinder source to a CSIPersistentVolumeSource
func (t *osCinderCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.Cinder == nil {
		return nil, fmt.Errorf("pv is nil or Cinder not defined on pv")
	}

	cinderSource := pv.Spec.Cinder

	csiSource := &v1.CSIPersistentVolumeSource{
		Driver:           CinderDriverName,
		VolumeHandle:     cinderSource.VolumeID,
		ReadOnly:         cinderSource.ReadOnly,
		FSType:           cinderSource.FSType,
		VolumeAttributes: map[string]string{},
	}

	if err := translateTopologyFromInTreeToCSI(pv, CinderTopologyKey); err != nil {
		return nil, fmt.Errorf("failed to translate topology: %v", err)
	}

	pv.Spec.Cinder = nil
	pv.Spec.CSI = csiSource
	return pv, nil
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and
// translates the Cinder CSI source to a Cinder In-tree source.
func (t *osCinderCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
	}

	csiSource := pv.Spec.CSI

	cinderSource := &v1.CinderPersistentVolumeSource{
		VolumeID: csiSource.VolumeHandle,
		FSType:   csiSource.FSType,
		ReadOnly: csiSource.ReadOnly,
	}

	// translate CSI topology to In-tree topology for rollback compatibility.
	// It is not possible to guess Cinder Region from the Zone, therefore leave it empty.
	if err := translateTopologyFromCSIToInTree(pv, CinderTopologyKey, nil); err != nil {
		return nil, fmt.Errorf("failed to translate topology. PV:%+v. Error:%v", *pv, err)
	}

	pv.Spec.CSI = nil
	pv.Spec.Cinder = cinderSource
	return pv, nil
}


CanSupport

// CanSupport tests whether the plugin supports a given persistent volume
// specification from the API.  The spec pointer should be considered
// const.
func (t *osCinderCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
	return pv != nil && pv.Spec.Cinder != nil
}


CanSupportInline

// CanSupportInline tests whether the plugin supports a given inline volume
// specification from the API.  The spec pointer should be considered
// const.
func (t *osCinderCSITranslator) CanSupportInline(volume *v1.Volume) bool {
	return volume != nil && volume.Cinder != nil
}


GetInTreePluginName

// GetInTreePluginName returns the name of the intree plugin driver
func (t *osCinderCSITranslator) GetInTreePluginName() string {
	return CinderInTreePluginName
}


GetCSIPluginName

// GetCSIPluginName returns the name of the CSI plugin
func (t *osCinderCSITranslator) GetCSIPluginName() string {
	return CinderDriverName
}


RepairVolumeHandle

func (t *osCinderCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
	return volumeHandle, nil
}


NewPortworxCSITranslator

func NewPortworxCSITranslator() InTreePlugin {
	return &portworxCSITranslator{}
}


TranslateInTreeStorageClassToCSI

// TranslateInTreeStorageClassToCSI takes in-tree storage class used by in-tree plugin
// and translates them to a storageclass consumable by CSI plugin
func (p portworxCSITranslator) TranslateInTreeStorageClassToCSI(sc *storagev1.StorageClass) (*storagev1.StorageClass, error) {
	if sc == nil {
		return nil, fmt.Errorf("sc is nil")
	}
	sc.Provisioner = PortworxDriverName
	return sc, nil
}


TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes a inline volume and will translate
// the in-tree inline volume source to a CSIPersistentVolumeSource
func (p portworxCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil || volume.PortworxVolume == nil {
		return nil, fmt.Errorf("volume is nil or PortworxVolume not defined on volume")
	}

	var am v1.PersistentVolumeAccessMode
	if volume.PortworxVolume.ReadOnly {
		am = v1.ReadOnlyMany
	} else {
		am = v1.ReadWriteOnce
	}

	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			Name: fmt.Sprintf("%s-%s", PortworxDriverName, volume.PortworxVolume.VolumeID),
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeSource: v1.PersistentVolumeSource{
				CSI: &v1.CSIPersistentVolumeSource{
					Driver:           PortworxDriverName,
					VolumeHandle:     volume.PortworxVolume.VolumeID,
					FSType:           volume.PortworxVolume.FSType,
					VolumeAttributes: make(map[string]string),
				},
			},
			AccessModes: []v1.PersistentVolumeAccessMode{am},
		},
	}
	return pv, nil
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a Portworx persistent volume and will translate
// the in-tree pv source to a CSI Source
func (p portworxCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.PortworxVolume == nil {
		return nil, fmt.Errorf("pv is nil or PortworxVolume not defined on pv")
	}
	csiSource := &v1.CSIPersistentVolumeSource{
		Driver:           PortworxDriverName,
		VolumeHandle:     pv.Spec.PortworxVolume.VolumeID,
		FSType:           pv.Spec.PortworxVolume.FSType,
		VolumeAttributes: make(map[string]string), // copy access mode
	}
	pv.Spec.PortworxVolume = nil
	pv.Spec.CSI = csiSource

	return pv, nil
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with a CSI PersistentVolume Source and will translate
// it to a in-tree Persistent Volume Source for the in-tree volume
func (p portworxCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
	}
	csiSource := pv.Spec.CSI

	portworxSource := &v1.PortworxVolumeSource{
		VolumeID: csiSource.VolumeHandle,
		FSType:   csiSource.FSType,
		ReadOnly: csiSource.ReadOnly,
	}
	pv.Spec.CSI = nil
	pv.Spec.PortworxVolume = portworxSource

	return pv, nil
}


CanSupport

// CanSupport tests whether the plugin supports a given persistent volume
// specification from the API.
func (p portworxCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
	return pv != nil && pv.Spec.PortworxVolume != nil
}


CanSupportInline

// CanSupportInline tests whether the plugin supports a given inline volume
// specification from the API.
func (p portworxCSITranslator) CanSupportInline(volume *v1.Volume) bool {
	return volume != nil && volume.PortworxVolume != nil
}


GetInTreePluginName

// GetInTreePluginName returns the in-tree plugin name this migrates
func (p portworxCSITranslator) GetInTreePluginName() string {
	return PortworxVolumePluginName
}


GetCSIPluginName

// GetCSIPluginName returns the name of the CSI plugin that supersedes the in-tree plugin
func (p portworxCSITranslator) GetCSIPluginName() string {
	return PortworxDriverName
}


RepairVolumeHandle

// RepairVolumeHandle generates a correct volume handle based on node ID information.
func (p portworxCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
	return volumeHandle, nil
}


NewRBDCSITranslator

func NewRBDCSITranslator() InTreePlugin {
	return &rbdCSITranslator{}
}


TranslateInTreeStorageClassToCSI

// TranslateInTreeStorageClassToCSI takes in-tree storage class used by in-tree plugin
// and translates them to a storage class consumable by CSI plugin
func (p rbdCSITranslator) TranslateInTreeStorageClassToCSI(sc *storagev1.StorageClass) (*storagev1.StorageClass, error) {
	if sc == nil {
		return nil, fmt.Errorf("sc is nil")
	}

	var params = map[string]string{}

	fillDefaultSCParams(params)
	for k, v := range sc.Parameters {
		switch strings.ToLower(k) {
		case fsTypeKey:
			params[csiFsTypeKey] = v
		case "imagefeatures":
			params[imgFeatureKey] = v
		case poolKey:
			params[poolKey] = v
		case "imageformat":
			params[imgFmtKey] = v
		case "adminid":
			params[adminIDKey] = v
		case "adminsecretname":
			params[provSecretNameKey] = v
			params[nodeStageSecretNameKey] = v
			params[cntrlExpandSecretNameKey] = v
		case "adminsecretnamespace":
			params[provSecretNamespaceKey] = v
			params[nodeStageSecretNamespaceKey] = v
			params[cntrlExpandSecretNamespaceKey] = v
		case monsKey:
			arr := strings.Split(v, ",")
			if len(arr) < 1 {
				return nil, fmt.Errorf("missing Ceph monitors")
			}
			params[monsKey] = v
			params[clusterIDKey] = fmt.Sprintf("%x", md5.Sum([]byte(v)))
		}
	}

	if params[provSecretNameKey] == "" {
		return nil, fmt.Errorf("missing Ceph admin secret name")
	}
	if params[monsKey] == "" {
		return nil, fmt.Errorf("missing Ceph monitors")
	}
	sc.Provisioner = RBDDriverName
	sc.Parameters = params
	return sc, nil
}


TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes an inline volume and will translate
// the in-tree inline volume source to a CSIPersistentVolumeSource
func (p rbdCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil || volume.RBD == nil {
		return nil, fmt.Errorf("volume is nil or RBDVolume not defined on volume")
	}

	var am v1.PersistentVolumeAccessMode
	if volume.RBD.ReadOnly {
		am = v1.ReadOnlyMany
	} else {
		am = v1.ReadWriteOnce
	}
	secRef := &v1.SecretReference{}
	if volume.RBD.SecretRef != nil {
		secRef.Name = volume.RBD.SecretRef.Name
		secRef.Namespace = podNamespace
	}
	volumeAttr := make(map[string]string)
	volumeAttr[clusterIDKey] = fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(volume.RBD.CephMonitors, ","))))
	volumeAttr[poolKey] = defaultPoolVal
	if volume.RBD.RBDPool != "" {
		volumeAttr[poolKey] = volume.RBD.RBDPool
	}
	volumeAttr[staticVolKey] = defaultMigStaticVal
	volumeAttr[imgFeatureKey] = defaultImgFeatureVal
	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			Name: fmt.Sprintf("%s-%s", RBDDriverName, volume.RBD.RBDImage),
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeSource: v1.PersistentVolumeSource{
				CSI: &v1.CSIPersistentVolumeSource{
					Driver:                    RBDDriverName,
					VolumeHandle:              volume.RBD.RBDImage,
					FSType:                    volume.RBD.FSType,
					VolumeAttributes:          volumeAttr,
					NodeStageSecretRef:        secRef,
					ControllerExpandSecretRef: secRef,
				},
			},
			AccessModes: []v1.PersistentVolumeAccessMode{am},
		},
	}
	return pv, nil
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a RBD persistent volume and will translate
// the in-tree pv source to a CSI Source
func (p rbdCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.RBD == nil {
		return nil, fmt.Errorf("pv is nil or RBD Volume not defined on pv")
	}
	var volID string
	volumeAttributes := make(map[string]string)

	if pv.Annotations[CSIRBDVolHandleAnnKey] != "" {
		volID = pv.Annotations[CSIRBDVolHandleAnnKey]
		volumeAttributes[clusterIDKey] = pv.Annotations[clusterIDKey]
	} else {
		mons := strings.Join(pv.Spec.RBD.CephMonitors, ",")
		pool := pv.Spec.RBD.RBDPool
		image := pv.Spec.RBD.RBDImage
		volumeAttributes[staticVolKey] = defaultMigStaticVal
		volumeAttributes[clusterIDKey] = fmt.Sprintf("%x", md5.Sum([]byte(mons)))
		volID = composeMigVolID(mons, pool, image)
	}

	err := fillVolAttrsForRequest(pv, volumeAttributes)
	if err != nil {
		return nil, err
	}
	if volumeAttributes[imgFeatureKey] == "" {
		volumeAttributes[imgFeatureKey] = defaultImgFeatureVal
	}
	var am v1.PersistentVolumeAccessMode
	if pv.Spec.RBD.ReadOnly {
		am = v1.ReadOnlyMany
	} else {
		am = v1.ReadWriteOnce
	}
	pv.Spec.AccessModes = []v1.PersistentVolumeAccessMode{am}
	csiSource := &v1.CSIPersistentVolumeSource{
		Driver:                    RBDDriverName,
		FSType:                    pv.Spec.RBD.FSType,
		VolumeHandle:              volID,
		VolumeAttributes:          volumeAttributes,
		NodeStageSecretRef:        pv.Spec.RBD.SecretRef,
		ControllerExpandSecretRef: pv.Spec.RBD.SecretRef,
	}
	pv.Spec.RBD = nil
	pv.Spec.CSI = csiSource
	return pv, nil
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with a CSI PersistentVolume Source and will translate
// it to an in-tree Persistent Volume Source for the in-tree volume
func (p rbdCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
	}
	var rbdImageName string
	monSlice := []string{""}
	csiSource := pv.Spec.CSI

	rbdImageName = csiSource.VolumeAttributes[imgNameKey]
	rbdPool := csiSource.VolumeAttributes[poolKey]
	radosUser := csiSource.VolumeAttributes[adminIDKey]
	if radosUser == "" {
		radosUser = defaultAdminUser
	}

	RBDSource := &v1.RBDPersistentVolumeSource{
		CephMonitors: monSlice,
		RBDImage:     rbdImageName,
		FSType:       csiSource.FSType,
		RBDPool:      rbdPool,
		RadosUser:    radosUser,
		ReadOnly:     csiSource.ReadOnly,
	}

	if pv.Annotations == nil {
		pv.Annotations = make(map[string]string)
	}
	fillAnnotationsFromCSISource(pv, csiSource)
	nodeSecret := csiSource.NodeStageSecretRef
	if nodeSecret != nil {
		RBDSource.SecretRef = &v1.SecretReference{Name: nodeSecret.Name, Namespace: nodeSecret.Namespace}
	}
	pv.Spec.CSI = nil
	pv.Spec.RBD = RBDSource

	return pv, nil
}


CanSupport

// CanSupport tests whether the plugin supports a given persistent volume
// specification from the API.
func (p rbdCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
	return pv != nil && pv.Spec.RBD != nil
}


CanSupportInline

// CanSupportInline tests whether the plugin supports a given inline volume
// specification from the API.
func (p rbdCSITranslator) CanSupportInline(volume *v1.Volume) bool {
	return volume != nil && volume.RBD != nil
}


GetInTreePluginName

// GetInTreePluginName returns the in-tree plugin name this migrates
func (p rbdCSITranslator) GetInTreePluginName() string {
	return RBDVolumePluginName
}


GetCSIPluginName

// GetCSIPluginName returns the name of the CSI plugin that supersedes the in-tree plugin
func (p rbdCSITranslator) GetCSIPluginName() string {
	return RBDDriverName
}


RepairVolumeHandle

// RepairVolumeHandle generates a correct volume handle based on node ID information.
func (p rbdCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
	return volumeHandle, nil
}


fillDefaultSCParams

// fillDefaultSCParams fills some sc parameters with default values
func fillDefaultSCParams(params map[string]string) {
	params[defaultMigKey] = defaultMigStaticVal
	params[poolKey] = defaultPoolVal
	params[provSecretNamespaceKey] = defaultAdminSecretNamespace
	params[cntrlExpandSecretNamespaceKey] = defaultAdminSecretNamespace
	params[nodeStageSecretNamespaceKey] = defaultAdminSecretNamespace
}


composeMigVolID

// composeMigVolID composes migration handle for RBD PV
// mig_mons-afcca55bc1bdd3f479be1e8281c13ab1_image-e0b45b52-7e09-47d3-8f1b-806995fa4412_7265706c696361706f6f6c
func composeMigVolID(mons string, pool string, image string) string {
	clusterIDInHandle := md5.Sum([]byte(mons))
	clusterField := monsPfx + fmt.Sprintf("%x", clusterIDInHandle)
	poolHashInHandle := hex.EncodeToString([]byte(pool))
	imageHashInHandle := strings.TrimPrefix(image, defaultIntreeImagePfx)
	imageField := imgPfx + imageHashInHandle
	volHash := strings.Join([]string{migVolPfx, clusterField, imageField, poolHashInHandle}, "_")
	return volHash
}


fillVolAttrsForRequest

// fillVolAttrsForRequest fill the volume attributes for node operations
func fillVolAttrsForRequest(pv *v1.PersistentVolume, volumeAttributes map[string]string) error {
	if pv == nil || pv.Spec.RBD == nil {
		return fmt.Errorf("pv is nil or RBD Volume not defined on pv")
	}
	volumeAttributes[imgNameKey] = pv.Spec.RBD.RBDImage
	volumeAttributes[poolKey] = pv.Spec.RBD.RBDPool
	volumeAttributes[imgFeatureKey] = pv.Annotations[imgFeatureKey]
	volumeAttributes[imgFmtKey] = pv.Annotations[imgFmtKey]
	volumeAttributes[journalPoolKey] = pv.Annotations[journalPoolKey]
	volumeAttributes[defaultMigKey] = defaultMigStaticVal
	volumeAttributes["tryOtherMounters"] = defaultMigStaticVal
	return nil
}


fillAnnotationsFromCSISource

// fillAnnotationsFromCSISource capture required information from csi source
func fillAnnotationsFromCSISource(pv *v1.PersistentVolume, csiSource *v1.CSIPersistentVolumeSource) {
	pv.Annotations[CSIRBDVolHandleAnnKey] = csiSource.VolumeHandle
	pv.Annotations[clusterIDKey] = csiSource.VolumeAttributes[clusterIDKey]
	pv.Annotations[journalPoolKey] = csiSource.VolumeAttributes[journalPoolKey]
	pv.Annotations[imgFeatureKey] = csiSource.VolumeAttributes[imgFeatureKey]
	pv.Annotations[imgFmtKey] = csiSource.VolumeAttributes[imgFmtKey]
}


NewvSphereCSITranslator

// NewvSphereCSITranslator returns a new instance of vSphereCSITranslator
func NewvSphereCSITranslator() InTreePlugin {
	return &vSphereCSITranslator{}
}


TranslateInTreeStorageClassToCSI

// TranslateInTreeStorageClassToCSI translates InTree vSphere storage class parameters to CSI storage class
func (t *vSphereCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) {
	if sc == nil {
		return nil, fmt.Errorf("sc is nil")
	}
	var params = map[string]string{}
	for k, v := range sc.Parameters {
		switch strings.ToLower(k) {
		case fsTypeKey:
			params[csiFsTypeKey] = v
		case paramStoragePolicyName:
			params[paramStoragePolicyName] = v
		case "datastore":
			params[paramDatastore] = v
		case "diskformat":
			params[paramDiskFormat] = v
		case "hostfailurestotolerate":
			params[paramHostFailuresToTolerate] = v
		case "forceprovisioning":
			params[paramForceProvisioning] = v
		case "cachereservation":
			params[paramCacheReservation] = v
		case "diskstripes":
			params[paramDiskstripes] = v
		case "objectspacereservation":
			params[paramObjectspacereservation] = v
		case "iopslimit":
			params[paramIopslimit] = v
		default:
			klog.V(2).Infof("StorageClass parameter [name:%q, value:%q] is not supported", k, v)
		}
	}

	// This helps vSphere CSI driver to identify in-tree provisioner request vs CSI provisioner request
	// When this is true, Driver returns initialvolumefilepath in the VolumeContext, which is
	// used in TranslateCSIPVToInTree
	params[paramcsiMigration] = "true"
	// translate AllowedTopologies to vSphere CSI Driver topology
	if len(sc.AllowedTopologies) > 0 {
		newTopologies, err := translateAllowedTopologies(sc.AllowedTopologies, vSphereCSITopologyZoneKey)
		if err != nil {
			return nil, fmt.Errorf("failed translating allowed topologies: %v", err)
		}
		sc.AllowedTopologies = newTopologies
	}
	sc.Parameters = params
	return sc, nil
}


TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes a Volume with VsphereVolume set from in-tree
// and converts the VsphereVolume source to a CSIPersistentVolumeSource
func (t *vSphereCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil || volume.VsphereVolume == nil {
		return nil, fmt.Errorf("volume is nil or VsphereVolume not defined on volume")
	}
	pv := &v1.PersistentVolume{
		ObjectMeta: metav1.ObjectMeta{
			// Must be unique per disk as it is used as the unique part of the
			// staging path
			Name: fmt.Sprintf("%s-%s", VSphereDriverName, volume.VsphereVolume.VolumePath),
		},
		Spec: v1.PersistentVolumeSpec{
			PersistentVolumeSource: v1.PersistentVolumeSource{
				CSI: &v1.CSIPersistentVolumeSource{
					Driver:           VSphereDriverName,
					VolumeHandle:     volume.VsphereVolume.VolumePath,
					FSType:           volume.VsphereVolume.FSType,
					VolumeAttributes: make(map[string]string),
				},
			},
			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
		},
	}
	if volume.VsphereVolume.StoragePolicyName != "" {
		pv.Spec.CSI.VolumeAttributes[paramStoragePolicyName] = pv.Spec.VsphereVolume.StoragePolicyName
	}
	return pv, nil
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a PV with VsphereVolume set from in-tree
// and converts the VsphereVolume source to a CSIPersistentVolumeSource
func (t *vSphereCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.VsphereVolume == nil {
		return nil, fmt.Errorf("pv is nil or VsphereVolume not defined on pv")
	}
	csiSource := &v1.CSIPersistentVolumeSource{
		Driver:           VSphereDriverName,
		VolumeHandle:     pv.Spec.VsphereVolume.VolumePath,
		FSType:           pv.Spec.VsphereVolume.FSType,
		VolumeAttributes: make(map[string]string),
	}
	if pv.Spec.VsphereVolume.StoragePolicyName != "" {
		csiSource.VolumeAttributes[paramStoragePolicyName] = pv.Spec.VsphereVolume.StoragePolicyName
	}
	// translate in-tree topology to CSI topology for migration
	if err := translateTopologyFromInTreevSphereToCSI(pv, vSphereCSITopologyZoneKey, vSphereCSITopologyRegionKey); err != nil {
		return nil, fmt.Errorf("failed to translate topology: %v", err)
	}
	pv.Spec.VsphereVolume = nil
	pv.Spec.CSI = csiSource
	return pv, nil
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and
// translates the vSphere CSI source to a vSphereVolume source.
func (t *vSphereCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, fmt.Errorf("pv is nil or CSI source not defined on pv")
	}
	csiSource := pv.Spec.CSI
	vsphereVirtualDiskVolumeSource := &v1.VsphereVirtualDiskVolumeSource{
		FSType: csiSource.FSType,
	}
	volumeFilePath, ok := csiSource.VolumeAttributes[AttributeInitialVolumeFilepath]
	if ok {
		vsphereVirtualDiskVolumeSource.VolumePath = volumeFilePath
	}
	// translate CSI topology to In-tree topology for rollback compatibility.
	if err := translateTopologyFromCSIToInTreevSphere(pv, vSphereCSITopologyZoneKey, vSphereCSITopologyRegionKey); err != nil {
		return nil, fmt.Errorf("failed to translate topology. PV:%+v. Error:%v", *pv, err)
	}
	pv.Spec.CSI = nil
	pv.Spec.VsphereVolume = vsphereVirtualDiskVolumeSource
	return pv, nil
}


CanSupport

// CanSupport tests whether the plugin supports a given persistent volume
// specification from the API.
func (t *vSphereCSITranslator) CanSupport(pv *v1.PersistentVolume) bool {
	return pv != nil && pv.Spec.VsphereVolume != nil
}


CanSupportInline

// CanSupportInline tests whether the plugin supports a given inline volume
// specification from the API.
func (t *vSphereCSITranslator) CanSupportInline(volume *v1.Volume) bool {
	return volume != nil && volume.VsphereVolume != nil
}


GetInTreePluginName

// GetInTreePluginName returns the name of the in-tree plugin driver
func (t *vSphereCSITranslator) GetInTreePluginName() string {
	return VSphereInTreePluginName
}


GetCSIPluginName

// GetCSIPluginName returns the name of the CSI plugin
func (t *vSphereCSITranslator) GetCSIPluginName() string {
	return VSphereDriverName
}


RepairVolumeHandle

// RepairVolumeHandle is needed in VerifyVolumesAttached on the external attacher when we need to do strict volume
// handle matching to check VolumeAttachment attached status.
// vSphere volume does not need patch to help verify whether that volume is attached.
func (t *vSphereCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
	return volumeHandle, nil
}


translateTopologyFromInTreevSphereToCSI

// translateTopologyFromInTreevSphereToCSI converts existing zone labels or in-tree vsphere topology to
// vSphere CSI topology.
func translateTopologyFromInTreevSphereToCSI(pv *v1.PersistentVolume, csiTopologyKeyZone string, csiTopologyKeyRegion string) error {
	zoneLabel, regionLabel := getTopologyLabel(pv)

	// If Zone kubernetes topology exist, replace it to use csiTopologyKeyZone
	zones := getTopologyValues(pv, zoneLabel)
	if len(zones) > 0 {
		replaceTopology(pv, zoneLabel, csiTopologyKeyZone)
	} else {
		// if nothing is in the NodeAffinity, try to fetch the topology from PV labels
		if label, ok := pv.Labels[zoneLabel]; ok {
			if len(label) > 0 {
				addTopology(pv, csiTopologyKeyZone, []string{label})
			}
		}
	}

	// If region kubernetes topology exist, replace it to use csiTopologyKeyRegion
	regions := getTopologyValues(pv, regionLabel)
	if len(regions) > 0 {
		replaceTopology(pv, regionLabel, csiTopologyKeyRegion)
	} else {
		// if nothing is in the NodeAffinity, try to fetch the topology from PV labels
		if label, ok := pv.Labels[regionLabel]; ok {
			if len(label) > 0 {
				addTopology(pv, csiTopologyKeyRegion, []string{label})
			}
		}
	}
	return nil
}


translateTopologyFromCSIToInTreevSphere

// translateTopologyFromCSIToInTreevSphere converts CSI zone/region affinity rules to in-tree vSphere zone/region labels
func translateTopologyFromCSIToInTreevSphere(pv *v1.PersistentVolume,
	csiTopologyKeyZone string, csiTopologyKeyRegion string) error {
	zoneLabel, regionLabel := getTopologyLabel(pv)

	// Replace all CSI topology to Kubernetes Zone label
	err := replaceTopology(pv, csiTopologyKeyZone, zoneLabel)
	if err != nil {
		return fmt.Errorf("failed to replace CSI topology to Kubernetes topology, error: %v", err)
	}

	// Replace all CSI topology to Kubernetes Region label
	err = replaceTopology(pv, csiTopologyKeyRegion, regionLabel)
	if err != nil {
		return fmt.Errorf("failed to replace CSI topology to Kubernetes topology, error: %v", err)
	}

	zoneVals := getTopologyValues(pv, zoneLabel)
	if len(zoneVals) > 0 {
		if pv.Labels == nil {
			pv.Labels = make(map[string]string)
		}
		_, zoneOK := pv.Labels[zoneLabel]
		if !zoneOK {
			pv.Labels[zoneLabel] = zoneVals[0]
		}
	}
	regionVals := getTopologyValues(pv, regionLabel)
	if len(regionVals) > 0 {
		if pv.Labels == nil {
			pv.Labels = make(map[string]string)
		}
		_, regionOK := pv.Labels[regionLabel]
		if !regionOK {
			pv.Labels[regionLabel] = regionVals[0]
		}
	}
	return nil
}


New

// New creates a new CSITranslator which does real translation
// for "in-tree plugins <-> csi drivers"
func New() CSITranslator {
	return CSITranslator{}
}


TranslateInTreeStorageClassToCSI

// TranslateInTreeStorageClassToCSI takes in-tree Storage Class
// and translates it to a set of parameters consumable by CSI plugin
func (CSITranslator) TranslateInTreeStorageClassToCSI(inTreePluginName string, sc *storage.StorageClass) (*storage.StorageClass, error) {
	newSC := sc.DeepCopy()
	for _, curPlugin := range inTreePlugins {
		if inTreePluginName == curPlugin.GetInTreePluginName() {
			return curPlugin.TranslateInTreeStorageClassToCSI(newSC)
		}
	}
	return nil, fmt.Errorf("could not find in-tree storage class parameter translation logic for %#v", inTreePluginName)
}


TranslateInTreeInlineVolumeToCSI

// TranslateInTreeInlineVolumeToCSI takes a inline volume and will translate
// the in-tree volume source to a CSIPersistentVolumeSource (wrapped in a PV)
// if the translation logic has been implemented.
func (CSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) {
	if volume == nil {
		return nil, fmt.Errorf("persistent volume was nil")
	}
	for _, curPlugin := range inTreePlugins {
		if curPlugin.CanSupportInline(volume) {
			pv, err := curPlugin.TranslateInTreeInlineVolumeToCSI(volume, podNamespace)
			if err != nil {
				return nil, err
			}
			// Inline volumes only support PersistentVolumeFilesystem (and not block).
			// If VolumeMode has not been set explicitly by plugin-specific
			// translator, set it to Filesystem here.
			// This is only necessary for inline volumes as the default PV
			// initialization that populates VolumeMode does not apply to inline volumes.
			if pv.Spec.VolumeMode == nil {
				volumeMode := v1.PersistentVolumeFilesystem
				pv.Spec.VolumeMode = &volumeMode
			}
			return pv, nil
		}
	}
	return nil, fmt.Errorf("could not find in-tree plugin translation logic for %#v", volume.Name)
}


TranslateInTreePVToCSI

// TranslateInTreePVToCSI takes a persistent volume and will translate
// the in-tree source to a CSI Source if the translation logic
// has been implemented. The input persistent volume will not
// be modified
func (CSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil {
		return nil, errors.New("persistent volume was nil")
	}
	copiedPV := pv.DeepCopy()
	for _, curPlugin := range inTreePlugins {
		if curPlugin.CanSupport(copiedPV) {
			return curPlugin.TranslateInTreePVToCSI(copiedPV)
		}
	}
	return nil, fmt.Errorf("could not find in-tree plugin translation logic for %#v", copiedPV.Name)
}


TranslateCSIPVToInTree

// TranslateCSIPVToInTree takes a PV with a CSI PersistentVolume Source and will translate
// it to a in-tree Persistent Volume Source for the specific in-tree volume specified
// by the `Driver` field in the CSI Source. The input PV object will not be modified.
func (CSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) {
	if pv == nil || pv.Spec.CSI == nil {
		return nil, errors.New("CSI persistent volume was nil")
	}
	copiedPV := pv.DeepCopy()
	for driverName, curPlugin := range inTreePlugins {
		if copiedPV.Spec.CSI.Driver == driverName {
			return curPlugin.TranslateCSIPVToInTree(copiedPV)
		}
	}
	return nil, fmt.Errorf("could not find in-tree plugin translation logic for %s", copiedPV.Spec.CSI.Driver)
}


IsMigratableIntreePluginByName

// IsMigratableIntreePluginByName tests whether there is migration logic for the in-tree plugin
// whose name matches the given name
func (CSITranslator) IsMigratableIntreePluginByName(inTreePluginName string) bool {
	for _, curPlugin := range inTreePlugins {
		if curPlugin.GetInTreePluginName() == inTreePluginName {
			return true
		}
	}
	return false
}


IsMigratedCSIDriverByName

// IsMigratedCSIDriverByName tests whether there exists an in-tree plugin with logic
// to migrate to the CSI driver with given name
func (CSITranslator) IsMigratedCSIDriverByName(csiPluginName string) bool {
	if _, ok := inTreePlugins[csiPluginName]; ok {
		return true
	}
	return false
}


GetInTreePluginNameFromSpec

// GetInTreePluginNameFromSpec returns the plugin name
func (CSITranslator) GetInTreePluginNameFromSpec(pv *v1.PersistentVolume, vol *v1.Volume) (string, error) {
	if pv != nil {
		for _, curPlugin := range inTreePlugins {
			if curPlugin.CanSupport(pv) {
				return curPlugin.GetInTreePluginName(), nil
			}
		}
		return "", fmt.Errorf("could not find in-tree plugin name from persistent volume %v", pv)
	} else if vol != nil {
		for _, curPlugin := range inTreePlugins {
			if curPlugin.CanSupportInline(vol) {
				return curPlugin.GetInTreePluginName(), nil
			}
		}
		return "", fmt.Errorf("could not find in-tree plugin name from volume %v", vol)
	} else {
		return "", errors.New("both persistent volume and volume are nil")
	}
}


GetCSINameFromInTreeName

// GetCSINameFromInTreeName returns the name of a CSI driver that supersedes the
// in-tree plugin with the given name
func (CSITranslator) GetCSINameFromInTreeName(pluginName string) (string, error) {
	for csiDriverName, curPlugin := range inTreePlugins {
		if curPlugin.GetInTreePluginName() == pluginName {
			return csiDriverName, nil
		}
	}
	return "", fmt.Errorf("could not find CSI Driver name for plugin %v", pluginName)
}


GetInTreeNameFromCSIName

// GetInTreeNameFromCSIName returns the name of the in-tree plugin superseded by
// a CSI driver with the given name
func (CSITranslator) GetInTreeNameFromCSIName(pluginName string) (string, error) {
	if plugin, ok := inTreePlugins[pluginName]; ok {
		return plugin.GetInTreePluginName(), nil
	}
	return "", fmt.Errorf("could not find In-Tree driver name for CSI plugin %v", pluginName)
}


IsPVMigratable

// IsPVMigratable tests whether there is migration logic for the given Persistent Volume
func (CSITranslator) IsPVMigratable(pv *v1.PersistentVolume) bool {
	for _, curPlugin := range inTreePlugins {
		if curPlugin.CanSupport(pv) {
			return true
		}
	}
	return false
}


IsInlineMigratable

// IsInlineMigratable tests whether there is Migration logic for the given Inline Volume
func (CSITranslator) IsInlineMigratable(vol *v1.Volume) bool {
	for _, curPlugin := range inTreePlugins {
		if curPlugin.CanSupportInline(vol) {
			return true
		}
	}
	return false
}


RepairVolumeHandle

// RepairVolumeHandle generates a correct volume handle based on node ID information.
func (CSITranslator) RepairVolumeHandle(driverName, volumeHandle, nodeID string) (string, error) {
	if plugin, ok := inTreePlugins[driverName]; ok {
		return plugin.RepairVolumeHandle(volumeHandle, nodeID)
	}
	return "", fmt.Errorf("could not find In-Tree driver name for CSI plugin %v", driverName)
}


New

// New creates a new controller.
func New(
	ctx context.Context,
	name string,
	driver Driver,
	kubeClient kubernetes.Interface,
	informerFactory informers.SharedInformerFactory) Controller {
	logger := klog.LoggerWithName(klog.FromContext(ctx), "resource controller")
	rcInformer := informerFactory.Resource().V1alpha2().ResourceClasses()
	claimInformer := informerFactory.Resource().V1alpha2().ResourceClaims()
	schedulingCtxInformer := informerFactory.Resource().V1alpha2().PodSchedulingContexts()

	eventBroadcaster := record.NewBroadcaster()
	go func() {
		<-ctx.Done()
		eventBroadcaster.Shutdown()
	}()
	// TODO: use contextual logging in eventBroadcaster once it
	// supports it. There is a StartStructuredLogging API, but it
	// uses the global klog, which is worse than redirecting an unstructured
	// string into our logger, in particular during testing.
	eventBroadcaster.StartLogging(func(format string, args ...interface{}) {
		helper, logger := logger.WithCallStackHelper()
		helper()
		logger.V(2).Info(fmt.Sprintf(format, args...))
	})
	eventBroadcaster.StartRecordingToSink(&corev1types.EventSinkImpl{Interface: kubeClient.CoreV1().Events(v1.NamespaceAll)})
	eventRecorder := eventBroadcaster.NewRecorder(scheme.Scheme,
		v1.EventSource{Component: fmt.Sprintf("resource driver %s", name)})

	// The work queue contains either keys for claims or PodSchedulingContext objects.
	queue := workqueue.NewNamedRateLimitingQueue(
		workqueue.DefaultControllerRateLimiter(), fmt.Sprintf("%s-queue", name))

	// The mutation cache acts as an additional layer for the informer
	// cache and after an update made by the controller returns a more
	// recent copy until the informer catches up.
	claimInformerCache := claimInformer.Informer().GetIndexer()
	claimCache := cache.NewIntegerResourceVersionMutationCache(claimInformerCache, claimInformerCache, 60*time.Second,
		false /* only cache updated claims that exist in the informer cache */)

	ctrl := &controller{
		ctx:                 ctx,
		logger:              logger,
		name:                name,
		finalizer:           name + "/deletion-protection",
		driver:              driver,
		kubeClient:          kubeClient,
		rcLister:            rcInformer.Lister(),
		rcSynced:            rcInformer.Informer().HasSynced,
		claimCache:          claimCache,
		claimSynced:         claimInformer.Informer().HasSynced,
		schedulingCtxLister: schedulingCtxInformer.Lister(),
		schedulingCtxSynced: schedulingCtxInformer.Informer().HasSynced,
		queue:               queue,
		eventRecorder:       eventRecorder,
	}

	loggerV6 := logger.V(6)
	if loggerV6.Enabled() {
		resourceClaimLogger := klog.LoggerWithValues(loggerV6, "type", "ResourceClaim")
		_, _ = claimInformer.Informer().AddEventHandler(resourceEventHandlerFuncs(&resourceClaimLogger, ctrl))
		schedulingCtxLogger := klog.LoggerWithValues(loggerV6, "type", "PodSchedulingContext")
		_, _ = schedulingCtxInformer.Informer().AddEventHandler(resourceEventHandlerFuncs(&schedulingCtxLogger, ctrl))
	} else {
		_, _ = claimInformer.Informer().AddEventHandler(resourceEventHandlerFuncs(nil, ctrl))
		_, _ = schedulingCtxInformer.Informer().AddEventHandler(resourceEventHandlerFuncs(nil, ctrl))
	}

	return ctrl
}


resourceEventHandlerFuncs

func resourceEventHandlerFuncs(logger *klog.Logger, ctrl *controller) cache.ResourceEventHandlerFuncs {
	return cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			ctrl.add(logger, obj)
		},
		UpdateFunc: func(oldObj, newObj interface{}) {
			ctrl.update(logger, oldObj, newObj)
		},
		DeleteFunc: ctrl.delete,
	}
}


add

func (ctrl *controller) add(logger *klog.Logger, obj interface{}) {
	if logger != nil {
		logger.Info("new object", "content", prettyPrint(obj))
	}
	ctrl.addNewOrUpdated("Adding new work item", obj)
}


update

func (ctrl *controller) update(logger *klog.Logger, oldObj, newObj interface{}) {
	if logger != nil {
		diff := cmp.Diff(oldObj, newObj)
		logger.Info("updated object", "content", prettyPrint(newObj), "diff", diff)
	}
	ctrl.addNewOrUpdated("Adding updated work item", newObj)
}


addNewOrUpdated

func (ctrl *controller) addNewOrUpdated(msg string, obj interface{}) {
	objKey, err := getKey(obj)
	if err != nil {
		ctrl.logger.Error(err, "Failed to get key", "obj", obj)
		return
	}
	ctrl.logger.V(5).Info(msg, "key", objKey)
	ctrl.queue.Add(objKey)
}


delete

func (ctrl *controller) delete(obj interface{}) {
	objKey, err := getKey(obj)
	if err != nil {
		return
	}
	ctrl.logger.V(5).Info("Removing deleted work item", "key", objKey)
	ctrl.queue.Forget(objKey)
}


getKey

func getKey(obj interface{}) (string, error) {
	objKey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		return "", err
	}
	prefix := ""
	switch obj.(type) {
	case *resourcev1alpha2.ResourceClaim:
		prefix = claimKeyPrefix
	case *resourcev1alpha2.PodSchedulingContext:
		prefix = schedulingCtxKeyPrefix
	default:
		return "", fmt.Errorf("unexpected object: %T", obj)
	}

	return prefix + objKey, nil
}


Run

// Run starts the controller.
func (ctrl *controller) Run(workers int) {
	defer ctrl.queue.ShutDown()

	ctrl.logger.Info("Starting", "driver", ctrl.name)
	defer ctrl.logger.Info("Shutting down", "driver", ctrl.name)

	stopCh := ctrl.ctx.Done()

	if !cache.WaitForCacheSync(stopCh, ctrl.rcSynced, ctrl.claimSynced, ctrl.schedulingCtxSynced) {
		ctrl.logger.Error(nil, "Cannot sync caches")
		return
	}

	for i := 0; i < workers; i++ {
		go wait.Until(ctrl.sync, 0, stopCh)
	}

	<-stopCh
}


sync

// sync is the main worker.
func (ctrl *controller) sync() {
	key, quit := ctrl.queue.Get()
	if quit {
		return
	}
	defer ctrl.queue.Done(key)

	logger := klog.LoggerWithValues(ctrl.logger, "key", key)
	ctx := klog.NewContext(ctrl.ctx, logger)
	logger.V(4).Info("processing")
	obj, err := ctrl.syncKey(ctx, key.(string))
	switch err {
	case nil:
		logger.V(5).Info("completed")
		ctrl.queue.Forget(key)
	case errRequeue:
		logger.V(5).Info("requeue")
		ctrl.queue.AddRateLimited(key)
	case errPeriodic:
		logger.V(5).Info("recheck periodically")
		ctrl.queue.AddAfter(key, recheckDelay)
	default:
		logger.Error(err, "processing failed")
		if obj != nil {
			// TODO: We don't know here *what* failed. Determine based on error?
			ctrl.eventRecorder.Event(obj, v1.EventTypeWarning, "Failed", err.Error())
		}
		ctrl.queue.AddRateLimited(key)
	}
}


syncKey

// syncKey looks up a ResourceClaim by its key and processes it.
func (ctrl *controller) syncKey(ctx context.Context, key string) (obj runtime.Object, finalErr error) {
	sep := strings.Index(key, ":")
	if sep < 0 {
		return nil, fmt.Errorf("unexpected key: %s", key)
	}
	prefix, object := key[0:sep+1], key[sep+1:]
	namespace, name, err := cache.SplitMetaNamespaceKey(object)
	if err != nil {
		return nil, err
	}

	switch prefix {
	case claimKeyPrefix:
		claim, err := ctrl.getCachedClaim(ctx, object)
		if claim == nil || err != nil {
			return nil, err
		}
		obj, finalErr = claim, ctrl.syncClaim(ctx, claim)
	case schedulingCtxKeyPrefix:
		schedulingCtx, err := ctrl.schedulingCtxLister.PodSchedulingContexts(namespace).Get(name)
		if err != nil {
			if k8serrors.IsNotFound(err) {
				klog.FromContext(ctx).V(5).Info("PodSchedulingContext was deleted, no need to process it")
				return nil, nil
			}
			return nil, err
		}
		obj, finalErr = schedulingCtx, ctrl.syncPodSchedulingContexts(ctx, schedulingCtx)
	}
	return
}


getCachedClaim

func (ctrl *controller) getCachedClaim(ctx context.Context, key string) (*resourcev1alpha2.ResourceClaim, error) {
	claimObj, exists, err := ctrl.claimCache.GetByKey(key)
	if !exists || k8serrors.IsNotFound(err) {
		klog.FromContext(ctx).V(5).Info("ResourceClaim not found, no need to process it")
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	claim, ok := claimObj.(*resourcev1alpha2.ResourceClaim)
	if !ok {
		return nil, fmt.Errorf("internal error: got %T instead of *resourcev1alpha2.ResourceClaim from claim cache", claimObj)
	}
	return claim, nil
}


syncClaim

// syncClaim determines which next action may be needed for a ResourceClaim
// and does it.
func (ctrl *controller) syncClaim(ctx context.Context, claim *resourcev1alpha2.ResourceClaim) error {
	var err error
	logger := klog.FromContext(ctx)

	if len(claim.Status.ReservedFor) > 0 {
		// In use. Nothing that we can do for it now.
		if loggerV6 := logger.V(6); loggerV6.Enabled() {
			loggerV6.Info("ResourceClaim in use", "reservedFor", claim.Status.ReservedFor)
		} else {
			logger.V(5).Info("ResourceClaim in use")
		}
		return nil
	}

	if claim.DeletionTimestamp != nil ||
		claim.Status.DeallocationRequested {
		// Ready for deallocation. We might have our finalizer set. The
		// finalizer is specific to the driver, therefore we know that
		// this claim is "ours" when the finalizer is set.
		hasFinalizer := ctrl.hasFinalizer(claim)
		logger.V(5).Info("ResourceClaim ready for deallocation", "deallocationRequested", claim.Status.DeallocationRequested, "deletionTimestamp", claim.DeletionTimestamp, "allocated", claim.Status.Allocation != nil, "hasFinalizer", hasFinalizer)
		if hasFinalizer {
			claim = claim.DeepCopy()
			if claim.Status.Allocation != nil {
				// Allocation was completed. Deallocate before proceeding.
				if err := ctrl.driver.Deallocate(ctx, claim); err != nil {
					return fmt.Errorf("deallocate: %v", err)
				}
				claim.Status.Allocation = nil
				claim.Status.DriverName = ""
				claim.Status.DeallocationRequested = false
				claim, err = ctrl.kubeClient.ResourceV1alpha2().ResourceClaims(claim.Namespace).UpdateStatus(ctx, claim, metav1.UpdateOptions{})
				if err != nil {
					return fmt.Errorf("remove allocation: %v", err)
				}
				ctrl.claimCache.Mutation(claim)
			} else {
				// Ensure that there is no on-going allocation.
				if err := ctrl.driver.Deallocate(ctx, claim); err != nil {
					return fmt.Errorf("stop allocation: %v", err)
				}
			}

			if claim.Status.DeallocationRequested {
				// Still need to remove it.
				claim.Status.DeallocationRequested = false
				claim, err = ctrl.kubeClient.ResourceV1alpha2().ResourceClaims(claim.Namespace).UpdateStatus(ctx, claim, metav1.UpdateOptions{})
				if err != nil {
					return fmt.Errorf("remove deallocation: %v", err)
				}
				ctrl.claimCache.Mutation(claim)
			}

			claim.Finalizers = ctrl.removeFinalizer(claim.Finalizers)
			claim, err = ctrl.kubeClient.ResourceV1alpha2().ResourceClaims(claim.Namespace).Update(ctx, claim, metav1.UpdateOptions{})
			if err != nil {
				return fmt.Errorf("remove finalizer: %v", err)
			}
			ctrl.claimCache.Mutation(claim)
		}

		// Nothing further to do. The apiserver should remove it shortly.
		return nil

	}

	if claim.Status.Allocation != nil {
		logger.V(5).Info("ResourceClaim is allocated")
		return nil
	}
	if claim.Spec.AllocationMode != resourcev1alpha2.AllocationModeImmediate {
		logger.V(5).Info("ResourceClaim waiting for first consumer")
		return nil
	}

	// We need the ResourceClass to determine whether we should allocate it.
	class, err := ctrl.rcLister.Get(claim.Spec.ResourceClassName)
	if err != nil {
		return err
	}
	if class.DriverName != ctrl.name {
		// Not ours *at the moment*. This can change, so requeue and
		// check again. We could trigger a faster check when the
		// ResourceClass changes, but that shouldn't occur much in
		// practice and thus isn't worth the effort.
		//
		// We use exponential backoff because it is unlikely that
		// the ResourceClass changes much.
		logger.V(5).Info("ResourceClaim is handled by other driver", "driver", class.DriverName)
		return errRequeue
	}

	// Check parameters.
	claimParameters, classParameters, err := ctrl.getParameters(ctx, claim, class)
	if err != nil {
		return err
	}

	return ctrl.allocateClaim(ctx, claim, claimParameters, class, classParameters, "", nil)
}


getParameters

func (ctrl *controller) getParameters(ctx context.Context, claim *resourcev1alpha2.ResourceClaim, class *resourcev1alpha2.ResourceClass) (claimParameters, classParameters interface{}, err error) {
	classParameters, err = ctrl.driver.GetClassParameters(ctx, class)
	if err != nil {
		err = fmt.Errorf("class parameters %s: %v", class.ParametersRef, err)
		return
	}
	claimParameters, err = ctrl.driver.GetClaimParameters(ctx, claim, class, classParameters)
	if err != nil {
		err = fmt.Errorf("claim parameters %s: %v", claim.Spec.ParametersRef, err)
		return
	}
	return
}


allocateClaim

func (ctrl *controller) allocateClaim(ctx context.Context,
	claim *resourcev1alpha2.ResourceClaim, claimParameters interface{},
	class *resourcev1alpha2.ResourceClass, classParameters interface{},
	selectedNode string,
	selectedUser *resourcev1alpha2.ResourceClaimConsumerReference) error {
	logger := klog.FromContext(ctx)

	if claim.Status.Allocation != nil {
		// This can happen when two PodSchedulingContext objects trigger
		// allocation attempts (first one wins) or when we see the
		// update of the PodSchedulingContext object.
		logger.V(5).Info("Claim already allocated, nothing to do")
		return nil
	}

	claim = claim.DeepCopy()
	if !ctrl.hasFinalizer(claim) {
		// Set finalizer before doing anything. We continue with the updated claim.
		logger.V(5).Info("Adding finalizer")
		claim.Finalizers = append(claim.Finalizers, ctrl.finalizer)
		var err error
		claim, err = ctrl.kubeClient.ResourceV1alpha2().ResourceClaims(claim.Namespace).Update(ctx, claim, metav1.UpdateOptions{})
		if err != nil {
			return fmt.Errorf("add finalizer: %v", err)
		}
		ctrl.claimCache.Mutation(claim)
	}

	logger.V(5).Info("Allocating")
	allocation, err := ctrl.driver.Allocate(ctx, claim, claimParameters, class, classParameters, selectedNode)
	if err != nil {
		return fmt.Errorf("allocate: %v", err)
	}
	claim.Status.Allocation = allocation
	claim.Status.DriverName = ctrl.name
	if selectedUser != nil {
		claim.Status.ReservedFor = append(claim.Status.ReservedFor, *selectedUser)
	}
	logger.V(6).Info("Updating claim after allocation", "claim", claim)
	claim, err = ctrl.kubeClient.ResourceV1alpha2().ResourceClaims(claim.Namespace).UpdateStatus(ctx, claim, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("add allocation: %v", err)
	}
	ctrl.claimCache.Mutation(claim)
	return nil
}


checkPodClaim

func (ctrl *controller) checkPodClaim(ctx context.Context, pod *v1.Pod, podClaim v1.PodResourceClaim) (*ClaimAllocation, error) {
	claimName := resourceclaim.Name(pod, &podClaim)
	key := pod.Namespace + "/" + claimName
	claim, err := ctrl.getCachedClaim(ctx, key)
	if claim == nil || err != nil {
		return nil, err
	}
	if podClaim.Source.ResourceClaimTemplateName != nil {
		if err := resourceclaim.IsForPod(pod, claim); err != nil {
			return nil, err
		}
	}
	if claim.Spec.AllocationMode != resourcev1alpha2.AllocationModeWaitForFirstConsumer {
		// Nothing to do for it as part of pod scheduling.
		return nil, nil
	}
	class, err := ctrl.rcLister.Get(claim.Spec.ResourceClassName)
	if err != nil {
		return nil, err
	}
	if class.DriverName != ctrl.name {
		return nil, nil
	}
	// Check parameters.
	claimParameters, classParameters, err := ctrl.getParameters(ctx, claim, class)
	if err != nil {
		return nil, err
	}
	return &ClaimAllocation{
		PodClaimName:    podClaim.Name,
		Claim:           claim,
		Class:           class,
		ClaimParameters: claimParameters,
		ClassParameters: classParameters,
	}, nil
}