github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/metadata_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provider_test
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  
    10  	"github.com/juju/collections/set"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	core "k8s.io/api/core/v1"
    14  	storagev1 "k8s.io/api/storage/v1"
    15  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/runtime"
    17  	"k8s.io/client-go/kubernetes/fake"
    18  
    19  	"github.com/juju/juju/caas/kubernetes"
    20  	"github.com/juju/juju/caas/kubernetes/provider"
    21  	"github.com/juju/juju/environs"
    22  )
    23  
    24  type K8sMetadataSuite struct {
    25  	BaseSuite
    26  }
    27  
    28  var _ = gc.Suite(&K8sMetadataSuite{})
    29  
    30  var (
    31  	annotatedOperatorStorage = &storagev1.StorageClass{
    32  		ObjectMeta: v1.ObjectMeta{
    33  			Name: "operator-storage",
    34  			Annotations: map[string]string{
    35  				"juju.is/operator-storage": "true",
    36  			},
    37  		},
    38  	}
    39  
    40  	annotatedWorkloadStorage = &storagev1.StorageClass{
    41  		ObjectMeta: v1.ObjectMeta{
    42  			Name: "workload-storage",
    43  			Annotations: map[string]string{
    44  				"juju.is/workload-storage": "true",
    45  			},
    46  		},
    47  	}
    48  
    49  	azureNode = newNode(map[string]string{
    50  		"failure-domain.beta.kubernetes.io/region": "wallyworld-region",
    51  		"kubernetes.azure.com/cluster":             "true",
    52  	})
    53  
    54  	azureStorageClass = &storagev1.StorageClass{
    55  		ObjectMeta: v1.ObjectMeta{
    56  			Name: "mynode",
    57  		},
    58  		Provisioner: "kubernetes.io/azure-disk",
    59  	}
    60  
    61  	defaultStorage = &storagev1.StorageClass{
    62  		ObjectMeta: v1.ObjectMeta{
    63  			Name: "default",
    64  			Annotations: map[string]string{
    65  				"storageclass.kubernetes.io/is-default-class": "true",
    66  			},
    67  		},
    68  	}
    69  
    70  	ec2Node = newNode(map[string]string{
    71  		"failure-domain.beta.kubernetes.io/region": "wallyworld-region",
    72  		"manufacturer": "amazon_ec2",
    73  	})
    74  
    75  	ec2StorageClass = &storagev1.StorageClass{
    76  		ObjectMeta: v1.ObjectMeta{
    77  			Name: "mynode",
    78  		},
    79  		Provisioner: "kubernetes.io/aws-ebs",
    80  	}
    81  
    82  	gceNode = newNode(map[string]string{
    83  		"failure-domain.beta.kubernetes.io/region": "wallyworld-region",
    84  		"cloud.google.com/gke-nodepool":            "true",
    85  		"cloud.google.com/gke-os-distribution":     "true",
    86  	})
    87  
    88  	gceStorageClass = &storagev1.StorageClass{
    89  		ObjectMeta: v1.ObjectMeta{
    90  			Name: "mynode",
    91  		},
    92  		Provisioner: "kubernetes.io/gce-pd",
    93  	}
    94  
    95  	microk8sNode = newNode(map[string]string{
    96  		"microk8s.io/cluster":                      "true",
    97  		"failure-domain.beta.kubernetes.io/region": "wallyworld-region",
    98  	})
    99  
   100  	microk8sStorageClass = &storagev1.StorageClass{
   101  		ObjectMeta: v1.ObjectMeta{
   102  			Name: "mynode",
   103  		},
   104  		Provisioner: "microk8s.io/hostpath",
   105  	}
   106  
   107  	nominatedStorage = &storagev1.StorageClass{
   108  		ObjectMeta: v1.ObjectMeta{
   109  			Name: "nominated",
   110  		},
   111  	}
   112  )
   113  
   114  func newNode(labels map[string]string) *core.Node {
   115  	n := core.Node{}
   116  	n.SetLabels(labels)
   117  	return &n
   118  }
   119  
   120  func (s *K8sMetadataSuite) TestMicrok8sFromNodeMeta(c *gc.C) {
   121  	node := core.Node{
   122  		ObjectMeta: v1.ObjectMeta{
   123  			Name:   "mynode",
   124  			Labels: map[string]string{"microk8s.io/cluster": "true"},
   125  		},
   126  	}
   127  	cloud, region := provider.GetCloudProviderFromNodeMeta(node)
   128  	c.Assert(cloud, gc.Equals, "microk8s")
   129  	c.Assert(region, gc.Equals, "localhost")
   130  }
   131  
   132  func (s *K8sMetadataSuite) TestMicrok8sWithRegionFromNodeMeta(c *gc.C) {
   133  	node := core.Node{
   134  		ObjectMeta: v1.ObjectMeta{
   135  			Name: "mynode",
   136  			Labels: map[string]string{
   137  				"microk8s.io/cluster":                      "true",
   138  				"failure-domain.beta.kubernetes.io/region": "somewhere",
   139  			},
   140  		},
   141  	}
   142  	cloud, region := provider.GetCloudProviderFromNodeMeta(node)
   143  	c.Assert(cloud, gc.Equals, "microk8s")
   144  	c.Assert(region, gc.Equals, "somewhere")
   145  }
   146  
   147  func (s *K8sMetadataSuite) TestK8sCloudCheckersValidationPass(c *gc.C) {
   148  	// CompileK8sCloudCheckers will panic if there is invalid requirement definition so check it by calling it.
   149  	cloudCheckers := provider.CompileK8sCloudCheckers()
   150  	c.Assert(cloudCheckers, gc.NotNil)
   151  }
   152  
   153  type hostRegionTestcase struct {
   154  	expectedCloud   string
   155  	expectedRegions set.Strings
   156  	node            *core.Node
   157  }
   158  
   159  var hostRegionsTestCases = []hostRegionTestcase{
   160  	{
   161  		expectedRegions: set.NewStrings(),
   162  		node:            newNode(map[string]string{}),
   163  	},
   164  	{
   165  		expectedRegions: set.NewStrings(),
   166  		node: newNode(map[string]string{
   167  			"cloud.google.com/gke-nodepool": "",
   168  		}),
   169  	},
   170  	{
   171  		expectedRegions: set.NewStrings(),
   172  		node: newNode(map[string]string{
   173  			"cloud.google.com/gke-os-distribution": "",
   174  		}),
   175  	},
   176  	{
   177  		expectedCloud:   "gce",
   178  		expectedRegions: set.NewStrings(""),
   179  		node: newNode(map[string]string{
   180  			"cloud.google.com/gke-nodepool":        "",
   181  			"cloud.google.com/gke-os-distribution": "",
   182  		}),
   183  	},
   184  	{
   185  		expectedCloud:   "gce",
   186  		expectedRegions: set.NewStrings(""),
   187  		node: newNode(map[string]string{
   188  			"juju.is/cloud": "gce",
   189  		}),
   190  	},
   191  	{
   192  		expectedCloud:   "ec2",
   193  		expectedRegions: set.NewStrings(""),
   194  		node: newNode(map[string]string{
   195  			"juju.is/cloud": "ec2",
   196  		}),
   197  	},
   198  	{
   199  		expectedCloud:   "azure",
   200  		expectedRegions: set.NewStrings(""),
   201  		node: newNode(map[string]string{
   202  			"juju.is/cloud": "azure",
   203  		}),
   204  	},
   205  	{
   206  		expectedCloud:   "azure",
   207  		expectedRegions: set.NewStrings(""),
   208  		node: newNode(map[string]string{
   209  			"kubernetes.azure.com/cluster": "",
   210  		}),
   211  	},
   212  	{
   213  		expectedCloud:   "ec2",
   214  		expectedRegions: set.NewStrings(""),
   215  		node: newNode(map[string]string{
   216  			"manufacturer": "amazon_ec2",
   217  		}),
   218  	},
   219  	{
   220  		expectedCloud:   "ec2",
   221  		expectedRegions: set.NewStrings(""),
   222  		node: newNode(map[string]string{
   223  			"eks.amazonaws.com/nodegroup": "any-node-group",
   224  		}),
   225  	},
   226  	{
   227  		expectedRegions: set.NewStrings(),
   228  		node: newNode(map[string]string{
   229  			"failure-domain.beta.kubernetes.io/region": "a-fancy-region",
   230  		}),
   231  	},
   232  	{
   233  		expectedRegions: set.NewStrings(),
   234  		node: newNode(map[string]string{
   235  			"failure-domain.beta.kubernetes.io/region": "a-fancy-region",
   236  			"cloud.google.com/gke-nodepool":            "",
   237  		}),
   238  	},
   239  	{
   240  		expectedRegions: set.NewStrings(),
   241  		node: newNode(map[string]string{
   242  			"failure-domain.beta.kubernetes.io/region": "a-fancy-region",
   243  			"cloud.google.com/gke-os-distribution":     "",
   244  		}),
   245  	},
   246  	{
   247  		expectedCloud:   "gce",
   248  		expectedRegions: set.NewStrings("a-fancy-region"),
   249  		node: newNode(map[string]string{
   250  			"failure-domain.beta.kubernetes.io/region": "a-fancy-region",
   251  			"cloud.google.com/gke-nodepool":            "",
   252  			"cloud.google.com/gke-os-distribution":     "",
   253  		}),
   254  	},
   255  	{
   256  		expectedCloud:   "azure",
   257  		expectedRegions: set.NewStrings("a-fancy-region"),
   258  		node: newNode(map[string]string{
   259  			"failure-domain.beta.kubernetes.io/region": "a-fancy-region",
   260  			"kubernetes.azure.com/cluster":             "",
   261  		}),
   262  	},
   263  	{
   264  		expectedCloud:   "ec2",
   265  		expectedRegions: set.NewStrings("a-fancy-region"),
   266  		node: newNode(map[string]string{
   267  			"failure-domain.beta.kubernetes.io/region": "a-fancy-region",
   268  			"manufacturer": "amazon_ec2",
   269  		}),
   270  	},
   271  }
   272  
   273  func (s *K8sMetadataSuite) TestListHostCloudRegions(c *gc.C) {
   274  	for _, v := range hostRegionsTestCases {
   275  		clientSet := fake.NewSimpleClientset(v.node)
   276  
   277  		metadata, err := provider.GetClusterMetadata(
   278  			context.TODO(),
   279  			"",
   280  			clientSet.CoreV1().Nodes(),
   281  			clientSet.StorageV1().StorageClasses(),
   282  		)
   283  		c.Check(err, jc.ErrorIsNil)
   284  		c.Check(metadata.Cloud, gc.Equals, v.expectedCloud)
   285  		c.Check(metadata.Regions, jc.DeepEquals, v.expectedRegions)
   286  	}
   287  }
   288  
   289  func (_ *K8sMetadataSuite) TestGetMetadataVariations(c *gc.C) {
   290  	tests := []struct {
   291  		Name             string
   292  		InitialObjects   []runtime.Object
   293  		NominatedStorage string
   294  		Result           kubernetes.ClusterMetadata
   295  	}{
   296  		// EC2 tests
   297  		{
   298  			Name: "Test ec2 cloud finds provisioner storage",
   299  			InitialObjects: []runtime.Object{
   300  				ec2Node,
   301  				ec2StorageClass,
   302  			},
   303  			Result: kubernetes.ClusterMetadata{
   304  				Cloud:                "ec2",
   305  				Regions:              set.NewStrings("wallyworld-region"),
   306  				WorkloadStorageClass: ec2StorageClass,
   307  				OperatorStorageClass: ec2StorageClass,
   308  			},
   309  		},
   310  		{
   311  			Name: "Test ec2 cloud prefers annotation storage",
   312  			InitialObjects: []runtime.Object{
   313  				ec2Node,
   314  				ec2StorageClass,
   315  				annotatedOperatorStorage,
   316  				annotatedWorkloadStorage,
   317  			},
   318  			Result: kubernetes.ClusterMetadata{
   319  				Cloud:                "ec2",
   320  				Regions:              set.NewStrings("wallyworld-region"),
   321  				WorkloadStorageClass: annotatedWorkloadStorage,
   322  				OperatorStorageClass: annotatedOperatorStorage,
   323  			},
   324  		},
   325  		{
   326  			Name: "Test ec2 cloud prefers annotation storage without workload",
   327  			InitialObjects: []runtime.Object{
   328  				ec2Node,
   329  				annotatedOperatorStorage,
   330  			},
   331  			Result: kubernetes.ClusterMetadata{
   332  				Cloud:                "ec2",
   333  				Regions:              set.NewStrings("wallyworld-region"),
   334  				WorkloadStorageClass: nil,
   335  				OperatorStorageClass: annotatedOperatorStorage,
   336  			},
   337  		},
   338  		{
   339  			Name: "Test ec2 cloud prefers nominated storage as first priority",
   340  			InitialObjects: []runtime.Object{
   341  				ec2Node,
   342  				ec2StorageClass,
   343  				annotatedOperatorStorage,
   344  				annotatedWorkloadStorage,
   345  				nominatedStorage,
   346  			},
   347  			NominatedStorage: "nominated",
   348  			Result: kubernetes.ClusterMetadata{
   349  				Cloud:                "ec2",
   350  				Regions:              set.NewStrings("wallyworld-region"),
   351  				WorkloadStorageClass: nominatedStorage,
   352  				OperatorStorageClass: nominatedStorage,
   353  			},
   354  		},
   355  		{
   356  			Name: "Test ec2 cloud with no found storage",
   357  			InitialObjects: []runtime.Object{
   358  				ec2Node,
   359  			},
   360  			Result: kubernetes.ClusterMetadata{
   361  				Cloud:                "ec2",
   362  				Regions:              set.NewStrings("wallyworld-region"),
   363  				WorkloadStorageClass: nil,
   364  				OperatorStorageClass: nil,
   365  			},
   366  		},
   367  		{
   368  			Name: "Test ec2 cloud with default storage",
   369  			InitialObjects: []runtime.Object{
   370  				ec2Node,
   371  				defaultStorage,
   372  			},
   373  			Result: kubernetes.ClusterMetadata{
   374  				Cloud:                "ec2",
   375  				Regions:              set.NewStrings("wallyworld-region"),
   376  				WorkloadStorageClass: defaultStorage,
   377  				OperatorStorageClass: defaultStorage,
   378  			},
   379  		},
   380  
   381  		// Microk8s
   382  		{
   383  			Name: "Test microk8s cloud finds provisioner storage",
   384  			InitialObjects: []runtime.Object{
   385  				microk8sNode,
   386  				microk8sStorageClass,
   387  			},
   388  			Result: kubernetes.ClusterMetadata{
   389  				Cloud:                "microk8s",
   390  				Regions:              set.NewStrings("wallyworld-region"),
   391  				WorkloadStorageClass: microk8sStorageClass,
   392  				OperatorStorageClass: microk8sStorageClass,
   393  			},
   394  		},
   395  		{
   396  			Name: "Test microk8s cloud prefers annotation storage",
   397  			InitialObjects: []runtime.Object{
   398  				microk8sNode,
   399  				microk8sStorageClass,
   400  				annotatedOperatorStorage,
   401  				annotatedWorkloadStorage,
   402  			},
   403  			Result: kubernetes.ClusterMetadata{
   404  				Cloud:                "microk8s",
   405  				Regions:              set.NewStrings("wallyworld-region"),
   406  				WorkloadStorageClass: annotatedWorkloadStorage,
   407  				OperatorStorageClass: annotatedOperatorStorage,
   408  			},
   409  		},
   410  		{
   411  			Name: "Test microk8s cloud prefers annotation storage without workload",
   412  			InitialObjects: []runtime.Object{
   413  				microk8sNode,
   414  				annotatedOperatorStorage,
   415  			},
   416  			Result: kubernetes.ClusterMetadata{
   417  				Cloud:                "microk8s",
   418  				Regions:              set.NewStrings("wallyworld-region"),
   419  				WorkloadStorageClass: nil,
   420  				OperatorStorageClass: annotatedOperatorStorage,
   421  			},
   422  		},
   423  		{
   424  			Name: "Test microk8s cloud prefers nominated storage as first priority",
   425  			InitialObjects: []runtime.Object{
   426  				microk8sNode,
   427  				microk8sStorageClass,
   428  				annotatedOperatorStorage,
   429  				annotatedWorkloadStorage,
   430  				nominatedStorage,
   431  			},
   432  			NominatedStorage: "nominated",
   433  			Result: kubernetes.ClusterMetadata{
   434  				Cloud:                "microk8s",
   435  				Regions:              set.NewStrings("wallyworld-region"),
   436  				WorkloadStorageClass: nominatedStorage,
   437  				OperatorStorageClass: nominatedStorage,
   438  			},
   439  		},
   440  		{
   441  			Name: "Test microk8s cloud with no found storage",
   442  			InitialObjects: []runtime.Object{
   443  				microk8sNode,
   444  			},
   445  			Result: kubernetes.ClusterMetadata{
   446  				Cloud:                "microk8s",
   447  				Regions:              set.NewStrings("wallyworld-region"),
   448  				WorkloadStorageClass: nil,
   449  				OperatorStorageClass: nil,
   450  			},
   451  		},
   452  		{
   453  			Name: "Test microk8s cloud doesn't use default storage",
   454  			InitialObjects: []runtime.Object{
   455  				microk8sNode,
   456  				defaultStorage,
   457  			},
   458  			Result: kubernetes.ClusterMetadata{
   459  				Cloud:                "microk8s",
   460  				Regions:              set.NewStrings("wallyworld-region"),
   461  				WorkloadStorageClass: nil,
   462  				OperatorStorageClass: nil,
   463  			},
   464  		},
   465  
   466  		// Azure
   467  		{
   468  			Name: "Test azure cloud finds provisioner storage",
   469  			InitialObjects: []runtime.Object{
   470  				azureNode,
   471  				azureStorageClass,
   472  			},
   473  			Result: kubernetes.ClusterMetadata{
   474  				Cloud:                "azure",
   475  				Regions:              set.NewStrings("wallyworld-region"),
   476  				WorkloadStorageClass: azureStorageClass,
   477  				OperatorStorageClass: azureStorageClass,
   478  			},
   479  		},
   480  		{
   481  			Name: "Test azure cloud prefers annotation storage",
   482  			InitialObjects: []runtime.Object{
   483  				azureNode,
   484  				azureStorageClass,
   485  				annotatedOperatorStorage,
   486  				annotatedWorkloadStorage,
   487  			},
   488  			Result: kubernetes.ClusterMetadata{
   489  				Cloud:                "azure",
   490  				Regions:              set.NewStrings("wallyworld-region"),
   491  				WorkloadStorageClass: annotatedWorkloadStorage,
   492  				OperatorStorageClass: annotatedOperatorStorage,
   493  			},
   494  		},
   495  		{
   496  			Name: "Test azure cloud prefers annotation storage without workload",
   497  			InitialObjects: []runtime.Object{
   498  				azureNode,
   499  				annotatedOperatorStorage,
   500  			},
   501  			Result: kubernetes.ClusterMetadata{
   502  				Cloud:                "azure",
   503  				Regions:              set.NewStrings("wallyworld-region"),
   504  				WorkloadStorageClass: nil,
   505  				OperatorStorageClass: annotatedOperatorStorage,
   506  			},
   507  		},
   508  		{
   509  			Name: "Test azure cloud prefers nominated storage as first priority",
   510  			InitialObjects: []runtime.Object{
   511  				azureNode,
   512  				azureStorageClass,
   513  				annotatedOperatorStorage,
   514  				annotatedWorkloadStorage,
   515  				nominatedStorage,
   516  			},
   517  			NominatedStorage: "nominated",
   518  			Result: kubernetes.ClusterMetadata{
   519  				Cloud:                "azure",
   520  				Regions:              set.NewStrings("wallyworld-region"),
   521  				WorkloadStorageClass: nominatedStorage,
   522  				OperatorStorageClass: nominatedStorage,
   523  			},
   524  		},
   525  		{
   526  			Name: "Test azure cloud with no found storage",
   527  			InitialObjects: []runtime.Object{
   528  				azureNode,
   529  			},
   530  			Result: kubernetes.ClusterMetadata{
   531  				Cloud:                "azure",
   532  				Regions:              set.NewStrings("wallyworld-region"),
   533  				WorkloadStorageClass: nil,
   534  				OperatorStorageClass: nil,
   535  			},
   536  		},
   537  		{
   538  			Name: "Test azure cloud with default storage",
   539  			InitialObjects: []runtime.Object{
   540  				azureNode,
   541  				defaultStorage,
   542  			},
   543  			Result: kubernetes.ClusterMetadata{
   544  				Cloud:                "azure",
   545  				Regions:              set.NewStrings("wallyworld-region"),
   546  				WorkloadStorageClass: defaultStorage,
   547  				OperatorStorageClass: defaultStorage,
   548  			},
   549  		},
   550  
   551  		// GCE
   552  		{
   553  			Name: "Test gce cloud finds provisioner storage",
   554  			InitialObjects: []runtime.Object{
   555  				gceNode,
   556  				gceStorageClass,
   557  			},
   558  			Result: kubernetes.ClusterMetadata{
   559  				Cloud:                "gce",
   560  				Regions:              set.NewStrings("wallyworld-region"),
   561  				WorkloadStorageClass: gceStorageClass,
   562  				OperatorStorageClass: gceStorageClass,
   563  			},
   564  		},
   565  		{
   566  			Name: "Test gce cloud prefers annotation storage",
   567  			InitialObjects: []runtime.Object{
   568  				gceNode,
   569  				gceStorageClass,
   570  				annotatedOperatorStorage,
   571  				annotatedWorkloadStorage,
   572  			},
   573  			Result: kubernetes.ClusterMetadata{
   574  				Cloud:                "gce",
   575  				Regions:              set.NewStrings("wallyworld-region"),
   576  				WorkloadStorageClass: annotatedWorkloadStorage,
   577  				OperatorStorageClass: annotatedOperatorStorage,
   578  			},
   579  		},
   580  		{
   581  			Name: "Test gce cloud prefers annotation storage without workload",
   582  			InitialObjects: []runtime.Object{
   583  				gceNode,
   584  				annotatedOperatorStorage,
   585  			},
   586  			Result: kubernetes.ClusterMetadata{
   587  				Cloud:                "gce",
   588  				Regions:              set.NewStrings("wallyworld-region"),
   589  				WorkloadStorageClass: nil,
   590  				OperatorStorageClass: annotatedOperatorStorage,
   591  			},
   592  		},
   593  		{
   594  			Name: "Test gce cloud prefers nominated storage as first priority",
   595  			InitialObjects: []runtime.Object{
   596  				gceNode,
   597  				gceStorageClass,
   598  				annotatedOperatorStorage,
   599  				annotatedWorkloadStorage,
   600  				nominatedStorage,
   601  			},
   602  			NominatedStorage: "nominated",
   603  			Result: kubernetes.ClusterMetadata{
   604  				Cloud:                "gce",
   605  				Regions:              set.NewStrings("wallyworld-region"),
   606  				WorkloadStorageClass: nominatedStorage,
   607  				OperatorStorageClass: nominatedStorage,
   608  			},
   609  		},
   610  		{
   611  			Name: "Test gce cloud with no found storage",
   612  			InitialObjects: []runtime.Object{
   613  				gceNode,
   614  			},
   615  			Result: kubernetes.ClusterMetadata{
   616  				Cloud:                "gce",
   617  				Regions:              set.NewStrings("wallyworld-region"),
   618  				WorkloadStorageClass: nil,
   619  				OperatorStorageClass: nil,
   620  			},
   621  		},
   622  		{
   623  			Name: "Test gce cloud with default storage",
   624  			InitialObjects: []runtime.Object{
   625  				gceNode,
   626  				defaultStorage,
   627  			},
   628  			Result: kubernetes.ClusterMetadata{
   629  				Cloud:                "gce",
   630  				Regions:              set.NewStrings("wallyworld-region"),
   631  				WorkloadStorageClass: defaultStorage,
   632  				OperatorStorageClass: defaultStorage,
   633  			},
   634  		},
   635  
   636  		// Other
   637  		{
   638  			Name: "Test other cloud prefers annotation storage",
   639  			InitialObjects: []runtime.Object{
   640  				newNode(map[string]string{}),
   641  				gceStorageClass,
   642  				annotatedOperatorStorage,
   643  				annotatedWorkloadStorage,
   644  			},
   645  			Result: kubernetes.ClusterMetadata{
   646  				Cloud:                "",
   647  				Regions:              set.NewStrings(),
   648  				WorkloadStorageClass: annotatedWorkloadStorage,
   649  				OperatorStorageClass: annotatedOperatorStorage,
   650  			},
   651  		},
   652  		{
   653  			Name: "Test other cloud prefers annotation storage without workload",
   654  			InitialObjects: []runtime.Object{
   655  				newNode(map[string]string{}),
   656  				annotatedOperatorStorage,
   657  			},
   658  			Result: kubernetes.ClusterMetadata{
   659  				Cloud:                "",
   660  				Regions:              set.NewStrings(),
   661  				WorkloadStorageClass: annotatedOperatorStorage,
   662  				OperatorStorageClass: annotatedOperatorStorage,
   663  			},
   664  		},
   665  		{
   666  			Name: "Test other cloud prefers nominated storage as first priority",
   667  			InitialObjects: []runtime.Object{
   668  				newNode(map[string]string{}),
   669  				gceStorageClass,
   670  				annotatedOperatorStorage,
   671  				annotatedWorkloadStorage,
   672  				nominatedStorage,
   673  			},
   674  			NominatedStorage: "nominated",
   675  			Result: kubernetes.ClusterMetadata{
   676  				Cloud:                "",
   677  				Regions:              set.NewStrings(),
   678  				WorkloadStorageClass: nominatedStorage,
   679  				OperatorStorageClass: nominatedStorage,
   680  			},
   681  		},
   682  		{
   683  			Name: "Test other cloud with no found storage",
   684  			InitialObjects: []runtime.Object{
   685  				newNode(map[string]string{}),
   686  			},
   687  			Result: kubernetes.ClusterMetadata{
   688  				Cloud:                "",
   689  				Regions:              set.NewStrings(),
   690  				WorkloadStorageClass: nil,
   691  				OperatorStorageClass: nil,
   692  			},
   693  		},
   694  		{
   695  			Name: "Test other cloud with default storage",
   696  			InitialObjects: []runtime.Object{
   697  				newNode(map[string]string{}),
   698  				defaultStorage,
   699  			},
   700  			Result: kubernetes.ClusterMetadata{
   701  				Cloud:                "",
   702  				Regions:              set.NewStrings(),
   703  				WorkloadStorageClass: defaultStorage,
   704  				OperatorStorageClass: defaultStorage,
   705  			},
   706  		},
   707  	}
   708  
   709  	for _, test := range tests {
   710  		c.Logf("running test %s", test.Name)
   711  		clientSet := fake.NewSimpleClientset(test.InitialObjects...)
   712  
   713  		metadata, err := provider.GetClusterMetadata(
   714  			context.TODO(),
   715  			test.NominatedStorage,
   716  			clientSet.CoreV1().Nodes(),
   717  			clientSet.StorageV1().StorageClasses(),
   718  		)
   719  		c.Assert(err, jc.ErrorIsNil)
   720  		c.Check(*metadata, jc.DeepEquals, test.Result)
   721  	}
   722  }
   723  
   724  func (s *K8sMetadataSuite) TestNominatedStorageNotFound(c *gc.C) {
   725  	clientSet := fake.NewSimpleClientset(
   726  		newNode(map[string]string{}),
   727  		gceStorageClass,
   728  		annotatedOperatorStorage,
   729  		annotatedWorkloadStorage,
   730  	)
   731  
   732  	_, err := provider.GetClusterMetadata(
   733  		context.TODO(),
   734  		"my-nominated-storage",
   735  		clientSet.CoreV1().Nodes(),
   736  		clientSet.StorageV1().StorageClasses(),
   737  	)
   738  
   739  	var notFoundError *environs.NominatedStorageNotFound
   740  	c.Assert(err, gc.NotNil)
   741  	c.Assert(errors.As(err, &notFoundError), jc.IsTrue)
   742  	c.Assert(notFoundError.StorageName, gc.Equals, "my-nominated-storage")
   743  }
   744  
   745  // TestNominatedStorageNotFoundWithNilStorageClasses is a regression test to
   746  // make sure that when no storage classes are defined and a nominated storage
   747  // class has been specified a NominatedStorageNotFoundError is returned.
   748  func (s *K8sMetadataSuite) TestNominatedStorageNotFoundWithNilStorageClasses(c *gc.C) {
   749  	clientSet := fake.NewSimpleClientset(
   750  		newNode(map[string]string{}),
   751  	)
   752  
   753  	_, err := provider.GetClusterMetadata(
   754  		context.TODO(),
   755  		"my-nominated-storage",
   756  		clientSet.CoreV1().Nodes(),
   757  		clientSet.StorageV1().StorageClasses(),
   758  	)
   759  
   760  	var notFoundError *environs.NominatedStorageNotFound
   761  	c.Assert(err, gc.NotNil)
   762  	c.Assert(errors.As(err, &notFoundError), jc.IsTrue)
   763  	c.Assert(notFoundError.StorageName, gc.Equals, "my-nominated-storage")
   764  }