github.com/ironcore-dev/gardener-extension-provider-ironcore@v0.3.2-0.20240314231816-8336447fb9a0/pkg/controller/controlplane/valuesprovider_test.go (about)

     1  // SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and IronCore contributors
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package controlplane
     5  
     6  import (
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/gardener/gardener/extensions/pkg/controller"
    11  	gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
    12  	extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
    13  	secretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager"
    14  	fakesecretsmanager "github.com/gardener/gardener/pkg/utils/secrets/manager/fake"
    15  	"github.com/ironcore-dev/ironcore/api/common/v1alpha1"
    16  	corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1"
    17  	storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1"
    18  	. "github.com/onsi/ginkgo/v2"
    19  	. "github.com/onsi/gomega"
    20  	corev1 "k8s.io/api/core/v1"
    21  	"k8s.io/apimachinery/pkg/api/resource"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/util/json"
    26  	"k8s.io/apimachinery/pkg/util/yaml"
    27  	"k8s.io/utils/ptr"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    30  	. "sigs.k8s.io/controller-runtime/pkg/envtest/komega"
    31  
    32  	apisironcore "github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/apis/ironcore"
    33  	"github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/internal"
    34  	"github.com/ironcore-dev/gardener-extension-provider-ironcore/pkg/ironcore"
    35  )
    36  
    37  var _ = Describe("Valueprovider Reconcile", func() {
    38  	ns, vp, cluster := SetupTest()
    39  
    40  	var (
    41  		fakeClient         client.Client
    42  		fakeSecretsManager secretsmanager.Interface
    43  	)
    44  
    45  	BeforeEach(func(ctx SpecContext) {
    46  		curDir, err := os.Getwd()
    47  		Expect(err).NotTo(HaveOccurred())
    48  		Expect(os.Chdir(filepath.Join("..", "..", ".."))).To(Succeed())
    49  		DeferCleanup(os.Chdir, curDir)
    50  
    51  		fakeClient = fakeclient.NewClientBuilder().Build()
    52  		fakeSecretsManager = fakesecretsmanager.New(fakeClient, ns.Name)
    53  		Expect(fakeClient.Create(ctx, &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "cloud-controller-manager-server", Namespace: ns.Name}})).To(Succeed())
    54  	})
    55  
    56  	Describe("#GetConfigChartValues", func() {
    57  		It("should return correct config chart values", func(ctx SpecContext) {
    58  			cp := &extensionsv1alpha1.ControlPlane{
    59  				ObjectMeta: metav1.ObjectMeta{
    60  					Name:      "control-plane",
    61  					Namespace: ns.Name,
    62  				},
    63  				Spec: extensionsv1alpha1.ControlPlaneSpec{
    64  					Region: "foo",
    65  					SecretRef: corev1.SecretReference{
    66  						Name:      "my-infra-creds",
    67  						Namespace: ns.Name,
    68  					},
    69  					DefaultSpec: extensionsv1alpha1.DefaultSpec{
    70  						Type: ironcore.Type,
    71  						ProviderConfig: &runtime.RawExtension{
    72  							Raw: encode(&apisironcore.ControlPlaneConfig{
    73  								CloudControllerManager: &apisironcore.CloudControllerManagerConfig{
    74  									FeatureGates: map[string]bool{
    75  										"CustomResourceValidation": true,
    76  									},
    77  								},
    78  							}),
    79  						},
    80  					},
    81  					InfrastructureProviderStatus: &runtime.RawExtension{
    82  						Raw: encode(&apisironcore.InfrastructureStatus{
    83  							NetworkRef: v1alpha1.LocalUIDReference{
    84  								Name: "my-network",
    85  								UID:  "1234",
    86  							},
    87  							PrefixRef: v1alpha1.LocalUIDReference{
    88  								Name: "my-prefix",
    89  								UID:  "6789",
    90  							},
    91  						}),
    92  					},
    93  				},
    94  			}
    95  			Expect(k8sClient.Create(ctx, cp)).To(Succeed())
    96  
    97  			By("ensuring that the provider ConfigMap has been created")
    98  			config := &corev1.ConfigMap{
    99  				ObjectMeta: metav1.ObjectMeta{
   100  					Namespace: ns.Name,
   101  					Name:      internal.CloudProviderConfigMapName,
   102  				},
   103  			}
   104  			Eventually(Get(config)).Should(Succeed())
   105  			Expect(config.Data).To(HaveKey("cloudprovider.conf"))
   106  			cloudProviderConfig := map[string]interface{}{}
   107  			Expect(yaml.Unmarshal([]byte(config.Data["cloudprovider.conf"]), &cloudProviderConfig)).NotTo(HaveOccurred())
   108  			Expect(cloudProviderConfig["networkName"]).To(Equal("my-network"))
   109  			Expect(cloudProviderConfig["prefixName"]).To(Equal("my-prefix"))
   110  			Expect(cloudProviderConfig["clusterName"]).To(Equal(cluster.Name))
   111  		})
   112  	})
   113  
   114  	Describe("#GetStorageClassesChartValues", func() {
   115  		BeforeEach(func(ctx SpecContext) {
   116  			By("creating an expand only VolumeClass")
   117  			volumeClassExpandOnly := &storagev1alpha1.VolumeClass{
   118  				ObjectMeta: metav1.ObjectMeta{
   119  					Name: "volume-expandable",
   120  				},
   121  				Capabilities: corev1alpha1.ResourceList{
   122  					corev1alpha1.ResourceIOPS: resource.MustParse("100"),
   123  					corev1alpha1.ResourceTPS:  resource.MustParse("100"),
   124  				},
   125  				ResizePolicy: storagev1alpha1.ResizePolicyExpandOnly,
   126  			}
   127  			Expect(k8sClient.Create(ctx, volumeClassExpandOnly)).To(Succeed())
   128  			DeferCleanup(k8sClient.Delete, volumeClassExpandOnly)
   129  
   130  			By("creating an static VolumeClass")
   131  			volumeClassStatic := &storagev1alpha1.VolumeClass{
   132  				ObjectMeta: metav1.ObjectMeta{
   133  					Name: "volume-static",
   134  				},
   135  				Capabilities: corev1alpha1.ResourceList{
   136  					corev1alpha1.ResourceIOPS: resource.MustParse("100"),
   137  					corev1alpha1.ResourceTPS:  resource.MustParse("100"),
   138  				},
   139  				ResizePolicy: storagev1alpha1.ResizePolicyStatic,
   140  			}
   141  			Expect(k8sClient.Create(ctx, volumeClassStatic)).To(Succeed())
   142  			DeferCleanup(k8sClient.Delete, volumeClassStatic)
   143  		})
   144  		It("should return an empty config chart value map if not storageclasses are present in the cloudprofile", func(ctx SpecContext) {
   145  			providerCloudProfile := &apisironcore.CloudProfileConfig{}
   146  			providerCloudProfileJson, err := json.Marshal(providerCloudProfile)
   147  			Expect(err).NotTo(HaveOccurred())
   148  
   149  			cluster := &controller.Cluster{
   150  				ObjectMeta: metav1.ObjectMeta{
   151  					Name: ns.Name,
   152  				},
   153  				CloudProfile: &gardencorev1beta1.CloudProfile{
   154  					Spec: gardencorev1beta1.CloudProfileSpec{
   155  						ProviderConfig: &runtime.RawExtension{
   156  							Raw: providerCloudProfileJson,
   157  						},
   158  					},
   159  				},
   160  			}
   161  
   162  			values, err := vp.GetStorageClassesChartValues(ctx, nil, cluster)
   163  			Expect(err).NotTo(HaveOccurred())
   164  			Expect(values).To(Equal(map[string]interface{}{
   165  				"storageClasses": []map[string]interface{}{},
   166  			}))
   167  		})
   168  
   169  		It("should return correct config chart values if default and additional storage classes are present in the cloudprofile", func(ctx SpecContext) {
   170  			providerCloudProfile := &apisironcore.CloudProfileConfig{
   171  				StorageClasses: apisironcore.StorageClasses{
   172  					Default: &apisironcore.StorageClass{
   173  						Name: "foo",
   174  						Type: "volume-expandable",
   175  					},
   176  					Additional: []apisironcore.StorageClass{
   177  						{
   178  							Name: "foo1",
   179  							Type: "volume-expandable",
   180  						},
   181  						{
   182  							Name: "foo2",
   183  							Type: "volume-static",
   184  						},
   185  					},
   186  				},
   187  			}
   188  			providerCloudProfileJson, err := json.Marshal(providerCloudProfile)
   189  			Expect(err).NotTo(HaveOccurred())
   190  
   191  			cluster := &controller.Cluster{
   192  				ObjectMeta: metav1.ObjectMeta{
   193  					Name: ns.Name,
   194  				},
   195  				CloudProfile: &gardencorev1beta1.CloudProfile{
   196  					Spec: gardencorev1beta1.CloudProfileSpec{
   197  						ProviderConfig: &runtime.RawExtension{
   198  							Raw: providerCloudProfileJson,
   199  						},
   200  					},
   201  				},
   202  			}
   203  
   204  			values, err := vp.GetStorageClassesChartValues(ctx, nil, cluster)
   205  			Expect(err).NotTo(HaveOccurred())
   206  			Expect(values).To(Equal(map[string]interface{}{
   207  				"storageClasses": []map[string]interface{}{
   208  					{
   209  						"name":       "foo",
   210  						"type":       "volume-expandable",
   211  						"default":    true,
   212  						"expandable": true,
   213  					},
   214  					{
   215  						"name":       "foo1",
   216  						"type":       "volume-expandable",
   217  						"expandable": true,
   218  					},
   219  					{
   220  						"name":       "foo2",
   221  						"type":       "volume-static",
   222  						"expandable": false,
   223  					},
   224  				},
   225  			}))
   226  		})
   227  
   228  		It("should return correct config chart values if only additional storage classes are present in the cloudprofile", func(ctx SpecContext) {
   229  			providerCloudProfile := &apisironcore.CloudProfileConfig{
   230  				StorageClasses: apisironcore.StorageClasses{
   231  					Additional: []apisironcore.StorageClass{
   232  						{
   233  							Name: "foo1",
   234  							Type: "volume-expandable",
   235  						},
   236  						{
   237  							Name: "foo2",
   238  							Type: "volume-static",
   239  						},
   240  					},
   241  				},
   242  			}
   243  			providerCloudProfileJson, err := json.Marshal(providerCloudProfile)
   244  			Expect(err).NotTo(HaveOccurred())
   245  
   246  			cluster := &controller.Cluster{
   247  				ObjectMeta: metav1.ObjectMeta{
   248  					Name: ns.Name,
   249  				},
   250  				CloudProfile: &gardencorev1beta1.CloudProfile{
   251  					Spec: gardencorev1beta1.CloudProfileSpec{
   252  						ProviderConfig: &runtime.RawExtension{
   253  							Raw: providerCloudProfileJson,
   254  						},
   255  					},
   256  				},
   257  			}
   258  
   259  			values, err := vp.GetStorageClassesChartValues(ctx, nil, cluster)
   260  			Expect(err).NotTo(HaveOccurred())
   261  			Expect(values).To(Equal(map[string]interface{}{
   262  				"storageClasses": []map[string]interface{}{
   263  					{
   264  						"name":       "foo1",
   265  						"type":       "volume-expandable",
   266  						"expandable": true,
   267  					},
   268  					{
   269  						"name":       "foo2",
   270  						"type":       "volume-static",
   271  						"expandable": false,
   272  					},
   273  				},
   274  			}))
   275  		})
   276  
   277  		It("should return error if volumeClass is not available", func(ctx SpecContext) {
   278  			providerCloudProfile := &apisironcore.CloudProfileConfig{
   279  				StorageClasses: apisironcore.StorageClasses{
   280  					Default: &apisironcore.StorageClass{
   281  						Name: "foo",
   282  						Type: "volume-non-existing",
   283  					},
   284  				},
   285  			}
   286  			providerCloudProfileJson, err := json.Marshal(providerCloudProfile)
   287  			Expect(err).NotTo(HaveOccurred())
   288  
   289  			cluster := &controller.Cluster{
   290  				ObjectMeta: metav1.ObjectMeta{
   291  					Name: ns.Name,
   292  				},
   293  				CloudProfile: &gardencorev1beta1.CloudProfile{
   294  					Spec: gardencorev1beta1.CloudProfileSpec{
   295  						ProviderConfig: &runtime.RawExtension{
   296  							Raw: providerCloudProfileJson,
   297  						},
   298  					},
   299  				},
   300  			}
   301  
   302  			_, err = vp.GetStorageClassesChartValues(ctx, nil, cluster)
   303  			Expect(err).To(MatchError("could not get resize policy from volumeclass : VolumeClass not found"))
   304  		})
   305  
   306  	})
   307  
   308  	Describe("#GetControlPlaneShootCRDsChartValues", func() {
   309  		It("should return correct config chart values", func(ctx SpecContext) {
   310  			values, err := vp.GetControlPlaneShootCRDsChartValues(ctx, nil, nil)
   311  			Expect(err).NotTo(HaveOccurred())
   312  			Expect(values).To(Equal(map[string]interface{}{}))
   313  		})
   314  	})
   315  
   316  	Describe("#GetControlPlaneShootChartValues", func() {
   317  		It("should return correct config chart values", func(ctx SpecContext) {
   318  			providerCloudProfile := &apisironcore.CloudProfileConfig{
   319  				StorageClasses: apisironcore.StorageClasses{
   320  					Default: &apisironcore.StorageClass{
   321  						Name: "foo",
   322  						Type: "volume-expandable",
   323  					},
   324  					Additional: []apisironcore.StorageClass{
   325  						{
   326  							Name: "bar",
   327  							Type: "volume-static",
   328  						},
   329  					},
   330  				},
   331  			}
   332  			providerCloudProfileJson, err := json.Marshal(providerCloudProfile)
   333  			Expect(err).NotTo(HaveOccurred())
   334  			cluster := &controller.Cluster{
   335  				CloudProfile: &gardencorev1beta1.CloudProfile{
   336  					Spec: gardencorev1beta1.CloudProfileSpec{
   337  						ProviderConfig: &runtime.RawExtension{
   338  							Raw: providerCloudProfileJson,
   339  						},
   340  					},
   341  				},
   342  				Shoot: &gardencorev1beta1.Shoot{
   343  					ObjectMeta: metav1.ObjectMeta{
   344  						Namespace: ns.Name,
   345  						Name:      "my-shoot",
   346  					},
   347  					Spec: gardencorev1beta1.ShootSpec{
   348  						Kubernetes: gardencorev1beta1.Kubernetes{
   349  							Version: "1.26.0",
   350  							VerticalPodAutoscaler: &gardencorev1beta1.VerticalPodAutoscaler{
   351  								Enabled: true,
   352  							},
   353  						},
   354  					},
   355  				},
   356  			}
   357  			values, err := vp.GetControlPlaneShootChartValues(ctx, nil, cluster, nil, nil)
   358  			Expect(err).NotTo(HaveOccurred())
   359  			Expect(values).To(Equal(map[string]interface{}{
   360  				"cloud-controller-manager": map[string]interface{}{
   361  					"enabled": true,
   362  				},
   363  				"csi-driver-node": map[string]interface{}{
   364  					"enabled":     true,
   365  					"vpaEnabled":  true,
   366  					"pspDisabled": true,
   367  				},
   368  			}))
   369  		})
   370  	})
   371  
   372  	Describe("#GetControlPlaneChartValues", func() {
   373  		It("should return correct config chart values", func(ctx SpecContext) {
   374  			cp := &extensionsv1alpha1.ControlPlane{
   375  				ObjectMeta: metav1.ObjectMeta{
   376  					Name:      "control-plane",
   377  					Namespace: ns.Name,
   378  				},
   379  				Spec: extensionsv1alpha1.ControlPlaneSpec{
   380  					Region: "foo",
   381  					SecretRef: corev1.SecretReference{
   382  						Name:      "my-infra-creds",
   383  						Namespace: ns.Name,
   384  					},
   385  					DefaultSpec: extensionsv1alpha1.DefaultSpec{
   386  						Type: ironcore.Type,
   387  						ProviderConfig: &runtime.RawExtension{
   388  							Raw: encode(&apisironcore.ControlPlaneConfig{
   389  								CloudControllerManager: &apisironcore.CloudControllerManagerConfig{
   390  									FeatureGates: map[string]bool{
   391  										"CustomResourceValidation": true,
   392  									},
   393  								},
   394  							}),
   395  						},
   396  					},
   397  					InfrastructureProviderStatus: &runtime.RawExtension{
   398  						Raw: encode(&apisironcore.InfrastructureStatus{
   399  							NetworkRef: v1alpha1.LocalUIDReference{
   400  								Name: "my-network",
   401  								UID:  "1234",
   402  							},
   403  						}),
   404  					},
   405  				},
   406  			}
   407  			providerCloudProfile := &apisironcore.CloudProfileConfig{
   408  				StorageClasses: apisironcore.StorageClasses{
   409  					Default: &apisironcore.StorageClass{
   410  						Name: "foo",
   411  						Type: "volumeTypeFoo",
   412  					},
   413  					Additional: []apisironcore.StorageClass{
   414  						{
   415  							Name: "bar",
   416  							Type: "volumeTypeBar",
   417  						},
   418  					},
   419  				},
   420  			}
   421  			providerCloudProfileJson, err := json.Marshal(providerCloudProfile)
   422  			Expect(err).NotTo(HaveOccurred())
   423  			networkProviderConfig := &unstructured.Unstructured{Object: map[string]any{
   424  				"kind":       "FooNetworkConfig",
   425  				"apiVersion": "v1alpha1",
   426  				"overlay": map[string]any{
   427  					"enabled": false,
   428  				},
   429  			}}
   430  			networkProviderConfigData, err := runtime.Encode(unstructured.UnstructuredJSONScheme, networkProviderConfig)
   431  			Expect(err).NotTo(HaveOccurred())
   432  			cluster := &controller.Cluster{
   433  				CloudProfile: &gardencorev1beta1.CloudProfile{
   434  					Spec: gardencorev1beta1.CloudProfileSpec{
   435  						ProviderConfig: &runtime.RawExtension{
   436  							Raw: providerCloudProfileJson,
   437  						},
   438  					},
   439  				},
   440  				Shoot: &gardencorev1beta1.Shoot{
   441  					ObjectMeta: metav1.ObjectMeta{
   442  						Namespace: ns.Name,
   443  						Name:      "my-shoot",
   444  					},
   445  					Spec: gardencorev1beta1.ShootSpec{
   446  						Networking: &gardencorev1beta1.Networking{
   447  							ProviderConfig: &runtime.RawExtension{Raw: networkProviderConfigData},
   448  							Pods:           ptr.To[string]("10.0.0.0/16"),
   449  						},
   450  						Kubernetes: gardencorev1beta1.Kubernetes{
   451  							Version: "1.26.0",
   452  							VerticalPodAutoscaler: &gardencorev1beta1.VerticalPodAutoscaler{
   453  								Enabled: true,
   454  							},
   455  						},
   456  					},
   457  				},
   458  			}
   459  
   460  			checksums := map[string]string{
   461  				ironcore.CloudProviderConfigName: "8bafb35ff1ac60275d62e1cbd495aceb511fb354f74a20f7d06ecb48b3a68432",
   462  			}
   463  			values, err := vp.GetControlPlaneChartValues(ctx, cp, cluster, fakeSecretsManager, checksums, false)
   464  			Expect(err).NotTo(HaveOccurred())
   465  			Expect(values).To(Equal(map[string]interface{}{
   466  				"global": map[string]interface{}{
   467  					"genericTokenKubeconfigSecretName": "generic-token-kubeconfig",
   468  				},
   469  				"cloud-controller-manager": map[string]interface{}{
   470  					"enabled":     true,
   471  					"replicas":    1,
   472  					"clusterName": ns.Name,
   473  					"podAnnotations": map[string]interface{}{
   474  						"checksum/secret-cloud-provider-config": "8bafb35ff1ac60275d62e1cbd495aceb511fb354f74a20f7d06ecb48b3a68432",
   475  					},
   476  					"podLabels": map[string]interface{}{
   477  						"maintenance.gardener.cloud/restart": "true",
   478  					},
   479  					"tlsCipherSuites": []string{
   480  						"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
   481  						"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
   482  						"TLS_AES_128_GCM_SHA256",
   483  						"TLS_AES_256_GCM_SHA384",
   484  						"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
   485  						"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
   486  						"TLS_CHACHA20_POLY1305_SHA256",
   487  						"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
   488  						"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
   489  					},
   490  					"secrets": map[string]interface{}{
   491  						"server": "cloud-controller-manager-server",
   492  					},
   493  					"featureGates": map[string]bool{
   494  						"CustomResourceValidation": true,
   495  					},
   496  					"podNetwork":           "10.0.0.0/16",
   497  					"configureCloudRoutes": true,
   498  				},
   499  				"csi-driver-controller": map[string]interface{}{
   500  					"enabled":  true,
   501  					"replicas": 1,
   502  				},
   503  			}))
   504  		})
   505  	})
   506  })
   507  
   508  func encode(obj runtime.Object) []byte {
   509  	data, _ := json.Marshal(obj)
   510  	return data
   511  }