Skip to content

Commit

Permalink
Merge pull request #108611 from divyenpatel/add-csi-topology-translat…
Browse files Browse the repository at this point in the history
…ion-for-in-tree-vSphere-volumes

topology translation of in-tree vSphere volume to vSphere CSI
  • Loading branch information
k8s-ci-robot committed Mar 17, 2022
2 parents 48ee98f + bd7f083 commit dd2596e
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 12 deletions.
96 changes: 95 additions & 1 deletion staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ const (
// VSphereInTreePluginName is the name of the in-tree plugin for vSphere Volume
VSphereInTreePluginName = "kubernetes.io/vsphere-volume"

// vSphereCSITopologyZoneKey is the zonal topology key for vSphere CSI Driver
vSphereCSITopologyZoneKey = "topology.csi.vmware.com/zone"

// vSphereCSITopologyRegionKey is the region topology key for vSphere CSI Driver
vSphereCSITopologyRegionKey = "topology.csi.vmware.com/region"

// paramStoragePolicyName used to supply SPBM Policy name for Volume provisioning
paramStoragePolicyName = "storagepolicyname"

Expand Down Expand Up @@ -104,7 +110,14 @@ func (t *vSphereCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.Stor
// When this is true, Driver returns initialvolumefilepath in the VolumeContext, which is
// used in TranslateCSIPVToInTree
params[paramcsiMigration] = "true"
// Note: sc.AllowedTopologies for Topology based volume provisioning will be supplied as it is.
// 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
}
Expand Down Expand Up @@ -154,6 +167,10 @@ func (t *vSphereCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (
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
Expand All @@ -173,6 +190,10 @@ func (t *vSphereCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (
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
Expand Down Expand Up @@ -206,3 +227,76 @@ func (t *vSphereCSITranslator) GetCSIPluginName() string {
func (t *vSphereCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) {
return volumeHandle, nil
}

// 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 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
}
216 changes: 205 additions & 11 deletions staging/src/k8s.io/csi-translation-lib/plugins/vsphere_volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,17 @@ func TestTranslatevSphereInTreeStorageClassToCSI(t *testing.T) {
Key: v1.LabelTopologyZone,
Values: []string{"zone-a"},
},
{
Key: v1.LabelTopologyRegion,
Values: []string{"region-a"},
},
}}
topologySelectorTermWithBetaLabels := v1.TopologySelectorTerm{[]v1.TopologySelectorLabelRequirement{
{
Key: v1.LabelFailureDomainBetaZone,
Values: []string{"zone-a"},
},
}}
expectedTopologySelectorTerm := v1.TopologySelectorTerm{[]v1.TopologySelectorLabelRequirement{
{
Key: v1.LabelFailureDomainBetaRegion,
Values: []string{"region-a"},
Key: vSphereCSITopologyZoneKey,
Values: []string{"zone-a"},
},
}}
cases := []struct {
Expand Down Expand Up @@ -88,27 +86,27 @@ func TestTranslatevSphereInTreeStorageClassToCSI(t *testing.T) {
{
name: "translate with no parameter and allowedTopology",
sc: NewStorageClass(map[string]string{}, []v1.TopologySelectorTerm{topologySelectorTerm}),
expSc: NewStorageClass(map[string]string{paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
expSc: NewStorageClass(map[string]string{paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
},
{
name: "translate with storagepolicyname and allowedTopology",
sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
},
{
name: "translate with storagepolicyname and allowedTopology beta labels",
sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name"}, []v1.TopologySelectorTerm{topologySelectorTermWithBetaLabels}),
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTermWithBetaLabels}),
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
},
{
name: "translate with raw vSAN policy parameters, datastore and diskformat",
sc: NewStorageClass(map[string]string{"hostfailurestotolerate": "2", "datastore": "vsanDatastore", "diskformat": "thin"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
expSc: NewStorageClass(map[string]string{"hostfailurestotolerate-migrationparam": "2", "datastore-migrationparam": "vsanDatastore", "diskformat-migrationparam": "thin", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
expSc: NewStorageClass(map[string]string{"hostfailurestotolerate-migrationparam": "2", "datastore-migrationparam": "vsanDatastore", "diskformat-migrationparam": "thin", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
},
{
name: "translate with all parameters",
sc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore": "test-datastore-name", "fstype": "ext4", "diskformat": "thin", "hostfailurestotolerate": "1", "forceprovisioning": "yes", "cachereservation": "25", "diskstripes": "4", "objectspacereservation": "10", "iopslimit": "32"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore-migrationparam": "test-datastore-name", "csi.storage.k8s.io/fstype": "ext4", "diskformat-migrationparam": "thin", "hostfailurestotolerate-migrationparam": "1", "forceprovisioning-migrationparam": "yes", "cachereservation-migrationparam": "25", "diskstripes-migrationparam": "4", "objectspacereservation-migrationparam": "10", "iopslimit-migrationparam": "32", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{topologySelectorTerm}),
expSc: NewStorageClass(map[string]string{"storagepolicyname": "test-policy-name", "datastore-migrationparam": "test-datastore-name", "csi.storage.k8s.io/fstype": "ext4", "diskformat-migrationparam": "thin", "hostfailurestotolerate-migrationparam": "1", "forceprovisioning-migrationparam": "yes", "cachereservation-migrationparam": "25", "diskstripes-migrationparam": "4", "objectspacereservation-migrationparam": "10", "iopslimit-migrationparam": "32", paramcsiMigration: "true"}, []v1.TopologySelectorTerm{expectedTopologySelectorTerm}),
},
}
for _, tc := range cases {
Expand Down Expand Up @@ -194,6 +192,84 @@ func TestTranslateVSphereCSIPVToInTree(t *testing.T) {
},
expErr: false,
},
{
name: "translate valid vSphere CSI PV with topology Node Affinity rules to vSphere CSI PV with topology labels",
csiPV: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
CSI: &v1.CSIPersistentVolumeSource{
Driver: VSphereDriverName,
VolumeHandle: "e4073a6d-642e-4dff-8f4a-b4e3a47c4bbd",
FSType: "ext4",
VolumeAttributes: map[string]string{
paramStoragePolicyName: "vSAN Default Storage Policy",
AttributeInitialVolumeFilepath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
},
},
},
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "topology.csi.vmware.com/zone",
Operator: v1.NodeSelectorOpIn,
Values: []string{"z1"},
},
{
Key: "topology.csi.vmware.com/region",
Operator: v1.NodeSelectorOpIn,
Values: []string{"r1"},
},
},
},
},
},
},
},
},
intreePV: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
Labels: map[string]string{"topology.kubernetes.io/zone": "z1", "topology.kubernetes.io/region": "r1"},
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
VolumePath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
FSType: "ext4",
},
},
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "topology.kubernetes.io/zone",
Operator: v1.NodeSelectorOpIn,
Values: []string{"z1"},
},
{
Key: "topology.kubernetes.io/region",
Operator: v1.NodeSelectorOpIn,
Values: []string{"r1"},
},
},
},
},
},
},
},
},
expErr: false,
},
}

for _, tc := range cases {
Expand Down Expand Up @@ -264,6 +340,124 @@ func TestTranslateVSphereInTreePVToCSI(t *testing.T) {
},
expErr: false,
},
{
name: "translate valid vSphere in-tree PV with beta topology labels to vSphere CSI PV",
intreePV: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
Labels: map[string]string{"failure-domain.beta.kubernetes.io/zone": "z1", "failure-domain.beta.kubernetes.io/region": "r1"},
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
VolumePath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
FSType: "ext4",
StoragePolicyName: "vSAN Default Storage Policy",
},
},
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
},
},
csiPV: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
Labels: map[string]string{"failure-domain.beta.kubernetes.io/zone": "z1", "failure-domain.beta.kubernetes.io/region": "r1"},
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
CSI: &v1.CSIPersistentVolumeSource{
Driver: VSphereDriverName,
VolumeHandle: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
FSType: "ext4",
VolumeAttributes: map[string]string{
paramStoragePolicyName: "vSAN Default Storage Policy",
},
},
},
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "topology.csi.vmware.com/zone",
Operator: v1.NodeSelectorOpIn,
Values: []string{"z1"},
},
{
Key: "topology.csi.vmware.com/region",
Operator: v1.NodeSelectorOpIn,
Values: []string{"r1"},
},
},
},
},
},
},
},
},
expErr: false,
},
{
name: "translate valid vSphere in-tree PV with GA topology labels to vSphere CSI PV",
intreePV: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
Labels: map[string]string{"topology.kubernetes.io/zone": "z1", "topology.kubernetes.io/region": "r1"},
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
VsphereVolume: &v1.VsphereVirtualDiskVolumeSource{
VolumePath: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
FSType: "ext4",
StoragePolicyName: "vSAN Default Storage Policy",
},
},
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
},
},
csiPV: &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "pvc-d8b4475f-2c47-486e-9b57-43ae006f9b59",
Labels: map[string]string{"topology.kubernetes.io/zone": "z1", "topology.kubernetes.io/region": "r1"},
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
CSI: &v1.CSIPersistentVolumeSource{
Driver: VSphereDriverName,
VolumeHandle: "[vsanDatastore] 6785a85e-268e-6352-a2e8-02008b7afadd/kubernetes-dynamic-pvc-68734c9f-a679-42e6-a694-39632c51e31f.vmdk",
FSType: "ext4",
VolumeAttributes: map[string]string{
paramStoragePolicyName: "vSAN Default Storage Policy",
},
},
},
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
NodeAffinity: &v1.VolumeNodeAffinity{
Required: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "topology.csi.vmware.com/zone",
Operator: v1.NodeSelectorOpIn,
Values: []string{"z1"},
},
{
Key: "topology.csi.vmware.com/region",
Operator: v1.NodeSelectorOpIn,
Values: []string{"r1"},
},
},
},
},
},
},
},
},
expErr: false,
},
}

for _, tc := range cases {
Expand Down

0 comments on commit dd2596e

Please sign in to comment.