Skip to content

Commit 76206e0

Browse files
committed
Populate volume capabilities in SPI CRs
1 parent db8995d commit 76206e0

5 files changed

Lines changed: 302 additions & 4 deletions

File tree

pkg/apis/cnsoperator/storagepolicyinfo/v1alpha1/storagepolicyinfo_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@ import (
3030
// +kubebuilder:validation:Enum=SupportsPersistentVolumeBlock;SupportsPersistentVolumeFilesystem;SupportsHighPerformanceLinkedClone;SupportsLinkedClone
3131
type VolumeCapability string
3232

33+
const (
34+
// SupportsVolumeModeBlock indicates that the policy supports PersistentVolume with Block volume mode.
35+
SupportsVolumeModeBlock VolumeCapability = "SupportsPersistentVolumeBlock"
36+
// SupportsVolumeModeFilesystem indicates that the policy supports PersistentVolume with Filesystem volume mode.
37+
SupportsVolumeModeFilesystem VolumeCapability = "SupportsPersistentVolumeFilesystem"
38+
// SupportsHighPerformanceLinkedClone indicates that the policy supports high-performance linked clones
39+
// on vSAN ESA clusters with ESXi 9.1 or above hosts.
40+
SupportsHighPerformanceLinkedClone VolumeCapability = "SupportsHighPerformanceLinkedClone"
41+
// SupportsLinkedClone indicates that the policy supports linked clones with at least one ESXi 9.1+ host.
42+
SupportsLinkedClone VolumeCapability = "SupportsLinkedClone"
43+
)
44+
3345
// Topology describes topology accessibility for the storage policy within a namespace.
3446
type Topology struct {
3547
// TopologyType describes the type of topology for the storage policy.

pkg/syncer/cnsoperator/controller/clusterstoragepolicyinfo/helper.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
commoncotypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common/commonco/types"
4343
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger"
4444
cnsoperatorutil "sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/util"
45+
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/vsphereinfra"
4546
)
4647

4748
// ownerReferenceKey returns OwnerReference key which is concatenated from the APIVersion, Kind and Name.
@@ -783,5 +784,6 @@ func isClusterESAEnabled(ctx context.Context, pc *property.Collector,
783784
esa := ok && cfgEx.VsanConfigInfo != nil &&
784785
cfgEx.VsanConfigInfo.VsanEsaEnabled != nil && *cfgEx.VsanConfigInfo.VsanEsaEnabled
785786
checkedClusters[clusterValue] = esa
787+
vsphereinfra.GetCache().SetClusterESAEnabled(clusterValue, esa)
786788
return esa, nil
787789
}

pkg/syncer/cnsoperator/controller/storagepolicyinfo/controller.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"time"
2525

2626
cnstypes "github.com/vmware/govmomi/cns/types"
27+
vimtypes "github.com/vmware/govmomi/vim25/types"
2728
v1 "k8s.io/api/core/v1"
2829
"k8s.io/apimachinery/pkg/api/equality"
2930
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -50,6 +51,7 @@ import (
5051
infraspiv1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator/infrastoragepolicyinfo/v1alpha1"
5152
storagepolicyv1alpha2 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator/storagepolicy/v1alpha2"
5253
spiv1alpha1 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/apis/cnsoperator/storagepolicyinfo/v1alpha1"
54+
cnsvsphere "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/vsphere"
5355
volumes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/cns-lib/volume"
5456
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config"
5557
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common"
@@ -58,6 +60,7 @@ import (
5860
k8s "sigs.k8s.io/vsphere-csi-driver/v3/pkg/kubernetes"
5961
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/types"
6062
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/util"
63+
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/vsphereinfra"
6164
)
6265

6366
// zonesProvider is the narrow slice of COCommonInterface that this controller
@@ -489,6 +492,18 @@ func (r *ReconcileStoragePolicyInfo) Reconcile(ctx context.Context,
489492
return r.completeReconciliationWithError(ctx, request.NamespacedName, timeout, err)
490493
}
491494

495+
// Populate VolumeCapabilities from InfraStoragePolicyInfo.
496+
if err := syncVolumeCapabilitiesFromInfraSPI(ctx, instance, infraSPI); err != nil {
497+
log.Errorf("Failed to sync volume capabilities for StoragePolicyInfo %q: %v",
498+
request.NamespacedName, err)
499+
if setErr := r.setSPIError(ctx, instance,
500+
fmt.Sprintf("Failed to sync volume capabilities: %v", err)); setErr != nil {
501+
log.Errorf("Failed to update StoragePolicyInfo %q status: %v",
502+
request.NamespacedName, setErr)
503+
}
504+
return r.completeReconciliationWithError(ctx, request.NamespacedName, timeout, err)
505+
}
506+
492507
if setErr := r.setSPISuccess(ctx, instance, "Successfully synced topology"); setErr != nil {
493508
log.Errorf("Failed to update StoragePolicyInfo %q status: %v",
494509
request.NamespacedName, setErr)
@@ -623,6 +638,165 @@ func (r *ReconcileStoragePolicyInfo) syncTopologyFromInfraSPI(ctx context.Contex
623638
return nil
624639
}
625640

641+
// syncVolumeCapabilitiesFromInfraSPI populates the namespace-scoped
642+
// StoragePolicyInfo volume capabilities from the cluster-scoped
643+
// InfraStoragePolicyInfo, with no additional vCenter calls.
644+
// SupportsVolumeModeFilesystem is always true, independent of InfraSPI.
645+
// SupportsVolumeModeBlock is cluster-wide (not namespace-filtered like topology), so
646+
// it is copied as-is from InfraSPI. SupportsLinkedClone and
647+
// SupportsHighPerformanceLinkedClone are recomputed for just the zones accessible to
648+
// this namespace; see linkedCloneCapabilitiesForNamespace.
649+
func syncVolumeCapabilitiesFromInfraSPI(ctx context.Context, instance *spiv1alpha1.StoragePolicyInfo,
650+
infraSPI *infraspiv1alpha1.InfraStoragePolicyInfo) error {
651+
infraCaps := infraSPI.Status.VolumeCapabilities
652+
653+
lc, hplc, err := linkedCloneCapabilitiesForNamespace(ctx, instance, infraSPI, infraCaps)
654+
if err != nil {
655+
return err
656+
}
657+
658+
instance.Status.VolumeCapabilities = map[spiv1alpha1.VolumeCapability]bool{
659+
spiv1alpha1.SupportsVolumeModeFilesystem: true,
660+
spiv1alpha1.SupportsVolumeModeBlock: infraCaps[infraspiv1alpha1.SupportsVolumeModeBlock],
661+
spiv1alpha1.SupportsLinkedClone: lc,
662+
spiv1alpha1.SupportsHighPerformanceLinkedClone: hplc,
663+
}
664+
return nil
665+
}
666+
667+
// linkedCloneCapabilitiesForNamespace determines SupportsLinkedClone and
668+
// SupportsHighPerformanceLinkedClone for this namespace's StoragePolicyInfo. When the
669+
// policy has no topology (non-zonal), there is no per-namespace zone subset to check
670+
// against, so the InfraSPI-computed cluster-wide results are used directly. Otherwise
671+
// both are recomputed over just the namespace's accessible zones (already
672+
// namespace-filtered by syncTopologyFromInfraSPI) via computeLinkedCloneForNamespace /
673+
// computeHPLCForNamespace.
674+
func linkedCloneCapabilitiesForNamespace(ctx context.Context, instance *spiv1alpha1.StoragePolicyInfo,
675+
infraSPI *infraspiv1alpha1.InfraStoragePolicyInfo,
676+
infraCaps map[infraspiv1alpha1.VolumeCapability]bool) (lc bool, hplc bool, err error) {
677+
if instance.Status.TopologyInfo == nil {
678+
return infraCaps[infraspiv1alpha1.SupportsLinkedClone],
679+
infraCaps[infraspiv1alpha1.SupportsHighPerformanceLinkedClone], nil
680+
}
681+
682+
nsZones := instance.Status.TopologyInfo.AccessibleZones
683+
lc, err = computeLinkedCloneForNamespace(ctx, infraSPI.Name, nsZones)
684+
if err != nil {
685+
return false, false, err
686+
}
687+
hplc, err = computeHPLCForNamespace(ctx, infraSPI.Name, nsZones, lc)
688+
if err != nil {
689+
return false, false, err
690+
}
691+
return lc, hplc, nil
692+
}
693+
694+
// computeLinkedCloneForNamespace determines whether the storage policy identified by
695+
// policyName (its K8s-compliant name) supports LinkedClone within nsZones, using only
696+
// data already cached by vsphereinfra.StoragePolicyInfoCache — no additional vCenter
697+
// calls are made. LinkedClone is supported for the namespace if any host mounting a
698+
// policy-compatible datastore within one of nsZones is running ESXi 9.1 or above.
699+
// See anyQualifyingHostForNamespace for the cache fields this relies on.
700+
func computeLinkedCloneForNamespace(ctx context.Context, policyName string, nsZones []string) (bool, error) {
701+
return anyQualifyingHostForNamespace(ctx, policyName, nsZones, hostSupportsLinkedClone)
702+
}
703+
704+
// computeHPLCForNamespace determines whether SupportsHighPerformanceLinkedClone is true
705+
// for nsZones. HPLC requires LinkedClone support first — if lcSupported is false, HPLC is
706+
// false without evaluating the vSAN-ESA condition. Otherwise it looks for a qualifying
707+
// host (ESXi 9.1+) whose cluster has vSAN-ESA enabled
708+
// (hostSupportsHighPerformanceLinkedClone), using only cached data — no additional
709+
// vCenter calls.
710+
func computeHPLCForNamespace(ctx context.Context, policyName string, nsZones []string,
711+
lcSupported bool) (bool, error) {
712+
if !lcSupported {
713+
return false, nil
714+
}
715+
return anyQualifyingHostForNamespace(ctx, policyName, nsZones, hostSupportsHighPerformanceLinkedClone)
716+
}
717+
718+
// anyQualifyingHostForNamespace walks every host mounting a datastore that is both
719+
// compatible with policyName and within one of nsZones, returning true as soon as
720+
// qualifies reports true for one of them. No additional vCenter calls are made:
721+
//
722+
// - policy → compatible datastores: DsToPolicy, populated by
723+
// util.GetPolicyCompatibleDatastoresPerZone/GetAccessibleZonesAndDatastoresForPolicy
724+
// while the clusterstoragepolicyinfo controller reconciles InfraSPI.
725+
// - zone → datastores: ZoneToDatastores, populated as a byproduct of the same call,
726+
// independent of any storage policy.
727+
// - datastore → hosts: DsToHosts, kept live by the PropertyCollector inventory watcher.
728+
func anyQualifyingHostForNamespace(ctx context.Context, policyName string, nsZones []string,
729+
qualifies func(ctx context.Context, hostID string) (bool, error)) (bool, error) {
730+
compatibleDS := vsphereinfra.GetCache().GetDatastoresForPolicy(policyName)
731+
if len(compatibleDS) == 0 {
732+
return false, nil
733+
}
734+
compatibleDSSet := make(map[string]struct{}, len(compatibleDS))
735+
for _, dsID := range compatibleDS {
736+
compatibleDSSet[dsID] = struct{}{}
737+
}
738+
739+
for _, zone := range nsZones {
740+
zoneDS, ok := vsphereinfra.GetCache().GetZoneDatastores(zone)
741+
if !ok {
742+
continue
743+
}
744+
for dsID := range zoneDS {
745+
if _, compatible := compatibleDSSet[dsID]; !compatible {
746+
continue
747+
}
748+
hosts, ok := vsphereinfra.GetCache().GetDsHosts(dsID)
749+
if !ok {
750+
continue
751+
}
752+
for hostID := range hosts {
753+
qualified, err := qualifies(ctx, hostID)
754+
if err != nil {
755+
return false, err
756+
}
757+
if qualified {
758+
return true, nil
759+
}
760+
}
761+
}
762+
}
763+
return false, nil
764+
}
765+
766+
// hostSupportsLinkedClone reports whether a host cached by the inventory watcher is
767+
// running ESXi 9.1 or above. Returns false if the host's version has not been observed
768+
// yet; returns an error if the cached version string can't be parsed.
769+
func hostSupportsLinkedClone(ctx context.Context, hostID string) (bool, error) {
770+
version, found := vsphereinfra.GetCache().GetHostVersion(hostID)
771+
if !found {
772+
return false, nil
773+
}
774+
supported, err := cnsvsphere.IsvSphereVersion91orAbove(ctx, vimtypes.AboutInfo{Version: version})
775+
if err != nil {
776+
return false, fmt.Errorf("failed to parse ESXi version %q for host %q: %w", version, hostID, err)
777+
}
778+
return supported, nil
779+
}
780+
781+
// hostSupportsHighPerformanceLinkedClone reports whether a host cached by the inventory
782+
// watcher both is ESXi 9.1+ and belongs to a cluster with vSAN-ESA enabled
783+
// (ClusterForHost/GetClusterESAEnabled, populated by the clusterstoragepolicyinfo
784+
// controller's isClusterESAEnabled check while reconciling InfraSPI). Returns false if
785+
// the host's cluster, or that cluster's vSAN-ESA state, has not been observed/computed
786+
// yet.
787+
func hostSupportsHighPerformanceLinkedClone(ctx context.Context, hostID string) (bool, error) {
788+
lc, err := hostSupportsLinkedClone(ctx, hostID)
789+
if err != nil || !lc {
790+
return false, err
791+
}
792+
clusterID, ok := vsphereinfra.GetCache().ClusterForHost(hostID)
793+
if !ok {
794+
return false, nil
795+
}
796+
esa, found := vsphereinfra.GetCache().GetClusterESAEnabled(clusterID)
797+
return found && esa, nil
798+
}
799+
626800
// syncMarkerPolicyTopology populates the namespace-scoped StoragePolicyInfo
627801
// topology for a marker policy by deriving accessible zones from FVS instance
628802
// namespaces that share the consumer namespace's VPC path, bypassing the

pkg/syncer/cnsoperator/util/util.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common/commonco"
4646
commoncotypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common/commonco/types"
4747
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger"
48+
"sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/cnsoperator/vsphereinfra"
4849
)
4950

5051
var virtualNetworkGVR = schema.GroupVersionResource{
@@ -661,6 +662,15 @@ func GetPolicyCompatibleDatastoresPerZone(ctx context.Context, topologyMgr commo
661662
zoneDS = append(zoneDS, clusterDS...)
662663
}
663664

665+
// zoneDS holds every datastore in the zone, independent of policy compatibility;
666+
// cache it so any policy's namespace-scoped capability checks can reuse it without
667+
// a further vCenter call.
668+
zoneDSIDs := make([]string, 0, len(zoneDS))
669+
for _, ds := range zoneDS {
670+
zoneDSIDs = append(zoneDSIDs, ds.Reference().Value)
671+
}
672+
vsphereinfra.GetCache().SetZoneDatastores(zone, zoneDSIDs)
673+
664674
// Filter to compatible datastores for this zone
665675
var compatibleDatastores []*cnsvsphere.DatastoreInfo
666676
for _, ds := range zoneDS {

0 commit comments

Comments
 (0)