@@ -20,6 +20,7 @@ import (
2020 "context"
2121 "fmt"
2222 "slices"
23+ "strings"
2324 "testing"
2425
2526 "github.com/container-storage-interface/spec/lib/go/csi"
@@ -1794,6 +1795,136 @@ func TestProvisionWithDeletedNodeFromCache(t *testing.T) {
17941795 }
17951796}
17961797
1798+ func TestNodeTopologyLabelFallback (t * testing.T ) {
1799+ // When the Node initially has incomplete topology labels, provisioning is expected to fail.
1800+ // Once those labels are added, provisioning should succeed.
1801+ topologyKeys := []string {
1802+ "com.example.csi/zone" ,
1803+ "com.example.csi/rack" ,
1804+ "com.example.csi/region" ,
1805+ "com.example.csi/compute-type" ,
1806+ }
1807+ incompleteNodeLabels := map [string ]string {
1808+ "com.example.csi/zone" : "zone1" ,
1809+ "com.example.csi/rack" : "rackA" ,
1810+ "com.example.csi/region" : "us-west" ,
1811+ }
1812+ completeNodeLabels := map [string ]string {
1813+ "com.example.csi/zone" : "zone1" ,
1814+ "com.example.csi/rack" : "rackA" ,
1815+ "com.example.csi/region" : "us-west" ,
1816+ "com.example.csi/compute-type" : "standard" ,
1817+ }
1818+
1819+ // originally the node has incomplete topology labels
1820+ node := & v1.Node {
1821+ ObjectMeta : metav1.ObjectMeta {
1822+ Name : "test-node" ,
1823+ Labels : incompleteNodeLabels ,
1824+ },
1825+ }
1826+ csiNode := & storagev1.CSINode {
1827+ ObjectMeta : metav1.ObjectMeta {
1828+ Name : "test-node" ,
1829+ },
1830+ Spec : storagev1.CSINodeSpec {
1831+ Drivers : []storagev1.CSINodeDriver {
1832+ {
1833+ Name : testDriverName ,
1834+ NodeID : "test-node" ,
1835+ TopologyKeys : topologyKeys ,
1836+ },
1837+ },
1838+ },
1839+ }
1840+ pvc := & v1.PersistentVolumeClaim {
1841+ ObjectMeta : metav1.ObjectMeta {
1842+ Name : "test-pvc" ,
1843+ Namespace : "default" ,
1844+ UID : "test-pvc-uid" ,
1845+ },
1846+ }
1847+ kubeClient := fakeclientset .NewSimpleClientset (node , csiNode , pvc )
1848+ _ , csiNodeLister , nodeLister , _ , _ , stopChan := listers (kubeClient )
1849+ defer close (stopChan )
1850+ pvcNodeStore := NewInMemoryStore ()
1851+
1852+ t .Run ("first provisioning attempt should fail because incomplete node labels" , func (t * testing.T ) {
1853+ _ , err := GenerateAccessibilityRequirements (
1854+ kubeClient ,
1855+ testDriverName ,
1856+ pvc .UID ,
1857+ pvc .Name ,
1858+ nil ,
1859+ "test-node" ,
1860+ false ,
1861+ false ,
1862+ csiNodeLister ,
1863+ nodeLister ,
1864+ pvcNodeStore ,
1865+ )
1866+ if err == nil {
1867+ t .Fatal ("expected error due to missing topology label, got nil" )
1868+ }
1869+ expectedError := "topology labels from selected node"
1870+ if ! strings .Contains (err .Error (), expectedError ) {
1871+ t .Errorf ("expected error containing %q, got: %v" , expectedError , err )
1872+ }
1873+ })
1874+
1875+ // update the Node with the missing label
1876+ node .Labels = completeNodeLabels
1877+ _ , err := kubeClient .CoreV1 ().Nodes ().Update (context .TODO (), node , metav1.UpdateOptions {})
1878+ if err != nil {
1879+ t .Fatalf ("failed to update node: %v" , err )
1880+ }
1881+ _ , csiNodeLister2 , nodeLister2 , _ , _ , stopChan2 := listers (kubeClient )
1882+ defer close (stopChan2 )
1883+
1884+ t .Run ("second provisioning attempt should succeed after labels added" , func (t * testing.T ) {
1885+ requirements , err := GenerateAccessibilityRequirements (
1886+ kubeClient ,
1887+ testDriverName ,
1888+ pvc .UID ,
1889+ pvc .Name ,
1890+ nil ,
1891+ "test-node" ,
1892+ false ,
1893+ false ,
1894+ csiNodeLister2 ,
1895+ nodeLister2 ,
1896+ pvcNodeStore ,
1897+ )
1898+ if err != nil {
1899+ t .Fatalf ("expected success after fallback, got error: %v" , err )
1900+ }
1901+ if requirements == nil {
1902+ t .Fatal ("expected requirements to be generated, got nil" )
1903+ }
1904+
1905+ expectedTopology := & csi.Topology {
1906+ Segments : map [string ]string {
1907+ "com.example.csi/zone" : "zone1" ,
1908+ "com.example.csi/rack" : "rackA" ,
1909+ "com.example.csi/region" : "us-west" ,
1910+ "com.example.csi/compute-type" : "standard" ,
1911+ },
1912+ }
1913+ if len (requirements .Requisite ) != 1 {
1914+ t .Errorf ("expected 1 requisite topology, got %d" , len (requirements .Requisite ))
1915+ } else if ! cmp .Equal (requirements .Requisite [0 ], expectedTopology , protocmp .Transform ()) {
1916+ t .Errorf ("requisite topology mismatch.\n Expected: %v\n Got: %v" ,
1917+ expectedTopology , requirements .Requisite [0 ])
1918+ }
1919+ if len (requirements .Preferred ) != 1 {
1920+ t .Errorf ("expected 1 preferred topology, got %d" , len (requirements .Preferred ))
1921+ } else if ! cmp .Equal (requirements .Preferred [0 ], expectedTopology , protocmp .Transform ()) {
1922+ t .Errorf ("preferred topology mismatch.\n Expected: %v\n Got: %v" ,
1923+ expectedTopology , requirements .Preferred [0 ])
1924+ }
1925+ })
1926+ }
1927+
17971928func BenchmarkDedupAndSortZone (b * testing.B ) {
17981929 terms := make ([]topologyTerm , 0 , 3000 )
17991930 for range 1000 {
0 commit comments