diff --git a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/values.yaml b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/values.yaml
index 60ff609a..02f0e30b 100644
--- a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/values.yaml
+++ b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/values.yaml
@@ -12,7 +12,6 @@ images:
csi-resizer: image-repository:image-tag
csi-liveness-probe: image-repository:image-tag
csi-snapshot-controller: image-repository:image-tag
- csi-snapshot-validation-webhook: image-repository:image-tag
socketPath: /var/lib/csi/sockets/pluginproxy
region: ""
diff --git a/charts/internal/shoot-system-components/charts/stackit-blockstorage-csi-driver/templates/daemonset.yaml b/charts/internal/shoot-system-components/charts/stackit-blockstorage-csi-driver/templates/daemonset.yaml
index cb04e3cf..103209a8 100644
--- a/charts/internal/shoot-system-components/charts/stackit-blockstorage-csi-driver/templates/daemonset.yaml
+++ b/charts/internal/shoot-system-components/charts/stackit-blockstorage-csi-driver/templates/daemonset.yaml
@@ -48,6 +48,12 @@ spec:
{{- end }}
- --v=2
- --provide-controller-service=false
+ {{- if .Values.csi.enableCompatibilityMode }}
+ - --legacy-storage-mode=true
+ {{- end }}
+ {{- if .Values.csi.blockLegacyCreation }}
+ - --legacy-volume-creation=false
+ {{- end }}
env:
- name: CSI_ENDPOINT
value: unix://{{ .Values.socketPath }}
@@ -62,7 +68,7 @@ spec:
allowPrivilegeEscalation: true
ports:
- name: healthz
- containerPort: 9908
+ containerPort: {{ .Values.healthzPort }}
protocol: TCP
livenessProbe:
httpGet:
@@ -112,7 +118,7 @@ spec:
args:
- --probe-timeout=3m
- --csi-address={{ .Values.socketPath }}
- - --health-port=9908
+ - --health-port={{ .Values.healthzPort }}
{{- if .Values.resources.livenessProbe }}
resources:
{{ toYaml .Values.resources.livenessProbe | indent 10 }}
diff --git a/charts/internal/shoot-system-components/charts/stackit-blockstorage-csi-driver/values.yaml b/charts/internal/shoot-system-components/charts/stackit-blockstorage-csi-driver/values.yaml
index c5e073af..d2ac0bad 100644
--- a/charts/internal/shoot-system-components/charts/stackit-blockstorage-csi-driver/values.yaml
+++ b/charts/internal/shoot-system-components/charts/stackit-blockstorage-csi-driver/values.yaml
@@ -3,6 +3,12 @@ driverName: block-storage.csi.stackit.cloud
rescanBlockStorageOnResize: "true"
+healthzPort: 9908
+
+csi:
+ enableCompatibilityMode: false
+ blockLegacyCreation: false
+
images:
csi-driver-stackit: image-repository:image-tag
csi-node-driver-registrar: image-repository:image-tag
diff --git a/hack/api-reference/api.md b/hack/api-reference/api.md
index 8bbbc206..773e7d08 100644
--- a/hack/api-reference/api.md
+++ b/hack/api-reference/api.md
@@ -215,11 +215,32 @@ string
+
+
+compatibilityMode
+
+string
+
+ |
+
+
+ |
+
+CSICompatibilityMode
+
+Underlying type: string
+
+
+
+
+
+
+
CSIManila
diff --git a/pkg/apis/stackit/v1alpha1/constants.go b/pkg/apis/stackit/v1alpha1/constants.go
index 86bbdc81..42400ae6 100644
--- a/pkg/apis/stackit/v1alpha1/constants.go
+++ b/pkg/apis/stackit/v1alpha1/constants.go
@@ -5,6 +5,8 @@ package v1alpha1
const (
// DefaultCSIName defines the default CSI (Container Storage Interface) name for STACKIT
DefaultCSIName = "stackit"
+ // DefaultCSICompatibilityMode defines the default CSI driver's compatibility mode.
+ DefaultCSICompatibilityMode = "default"
// DefaultCCMName defines the default CCM (Cloud Controller Manager) controller to use
DefaultCCMName = "stackit"
)
@@ -15,3 +17,11 @@ const (
STACKIT ControllerName = "stackit"
OPENSTACK ControllerName = "openstack"
)
+
+type CSICompatibilityMode string
+
+const (
+ DEFAULT CSICompatibilityMode = "default"
+ COMPAT CSICompatibilityMode = "compat"
+ COMPATBLOCK CSICompatibilityMode = "compatblock"
+)
diff --git a/pkg/apis/stackit/v1alpha1/defaults.go b/pkg/apis/stackit/v1alpha1/defaults.go
index d72cba93..e91caf34 100644
--- a/pkg/apis/stackit/v1alpha1/defaults.go
+++ b/pkg/apis/stackit/v1alpha1/defaults.go
@@ -43,4 +43,7 @@ func SetDefaults_ControlPlaneConfig(obj *ControlPlaneConfig) {
if obj.Storage.CSI.Name == "" {
obj.Storage.CSI.Name = DefaultCSIName
}
+ if obj.Storage.CSI.CompatibilityMode == "" {
+ obj.Storage.CSI.CompatibilityMode = DefaultCSICompatibilityMode
+ }
}
diff --git a/pkg/apis/stackit/v1alpha1/types_controlplane.go b/pkg/apis/stackit/v1alpha1/types_controlplane.go
index 5c10c28a..5b28637d 100644
--- a/pkg/apis/stackit/v1alpha1/types_controlplane.go
+++ b/pkg/apis/stackit/v1alpha1/types_controlplane.go
@@ -58,7 +58,8 @@ type Storage struct {
}
type CSI struct {
- Name string `json:"name"`
+ Name string `json:"name"`
+ CompatibilityMode string `json:"compatibilityMode,omitempty"`
}
// CSIManila contains configuration for CSI Manila driver (support for NFS volumes)
diff --git a/pkg/controller/controlplane/add.go b/pkg/controller/controlplane/add.go
index a0e11e6f..ab687708 100644
--- a/pkg/controller/controlplane/add.go
+++ b/pkg/controller/controlplane/add.go
@@ -43,10 +43,15 @@ type AddOptions struct {
// AddToManagerWithOptions adds a controller with the given Options to the given manager.
// The opts.Reconciler is being set with a newly instantiated actuator.
func AddToManagerWithOptions(ctx context.Context, mgr manager.Manager, opts AddOptions) error {
+ csiCompatibilityHandler, err := NewCompatCSICompatibilityHandler(mgr.GetClient(), mgr.GetConfig())
+ if err != nil {
+ return err
+ }
genericActuator, err := genericactuator.NewActuator(mgr, stackit.Name,
secretConfigsFunc, shootAccessSecretsFunc,
configChart, controlPlaneChart, controlPlaneShootChart, controlPlaneShootCRDsChart, storageClassChart,
- NewValuesProvider(mgr, DeployALBIngressController, opts.CustomLabelDomain), extensionscontroller.ChartRendererFactoryFunc(util.NewChartRendererForShoot),
+ NewValuesProvider(mgr, DeployALBIngressController, opts.CustomLabelDomain, csiCompatibilityHandler),
+ extensionscontroller.ChartRendererFactoryFunc(util.NewChartRendererForShoot),
imagevector.ImageVector(), "", nil, opts.WebhookServerNamespace)
if err != nil {
return err
diff --git a/pkg/controller/controlplane/csi_compatibility.go b/pkg/controller/controlplane/csi_compatibility.go
new file mode 100644
index 00000000..1b9a72f3
--- /dev/null
+++ b/pkg/controller/controlplane/csi_compatibility.go
@@ -0,0 +1,181 @@
+package controlplane
+
+import (
+ "context"
+ "fmt"
+ "path/filepath"
+
+ "github.com/gardener/gardener/pkg/chartrenderer"
+ gardenerutils "github.com/gardener/gardener/pkg/utils"
+ "github.com/gardener/gardener/pkg/utils/managedresources"
+ "k8s.io/client-go/rest"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "github.com/stackitcloud/gardener-extension-provider-stackit/v2/charts"
+ "github.com/stackitcloud/gardener-extension-provider-stackit/v2/imagevector"
+ stackitv1alpha1 "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/v1alpha1"
+ "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/openstack"
+)
+
+func NewCompatCSICompatibilityHandler(client client.Client, config *rest.Config) (*CompatCSICompatibilityHandler, error) {
+ renderer, err := chartrenderer.NewForConfig(config)
+ if err != nil {
+ return nil, err
+ }
+ return &CompatCSICompatibilityHandler{
+ client: client,
+ renderer: renderer,
+ }, nil
+}
+
+type CompatCSICompatibilityHandler struct {
+ client client.Client
+ renderer chartrenderer.Interface
+}
+
+func (ch *CompatCSICompatibilityHandler) HandleSeedCSICompatibility(ctx context.Context, namespace string, cpConfig *stackitv1alpha1.ControlPlaneConfig, controlPlaneValues map[string]any) error {
+ compatibilityMode := getCSICompatibilityMode(cpConfig)
+ if compatibilityMode != stackitv1alpha1.DEFAULT {
+ chart, err := ch.renderSeedCSICompatibilityMode(controlPlaneValues)
+ if err != nil {
+ return fmt.Errorf("failed to render seed CSI compatibility mode: %w", err)
+ }
+ err = ch.deploySeedCSICompatibilityMode(ctx, namespace, chart)
+ if err != nil {
+ return fmt.Errorf("failed to deploy seed CSI compatibility mode: %w", err)
+ }
+ } else {
+ err := ch.deleteSeedCSICompatibilityMode(ctx, namespace)
+ if err != nil {
+ return fmt.Errorf("failed to deploy seed CSI compatibility mode: %w", err)
+ }
+ }
+ return nil
+}
+
+func (ch *CompatCSICompatibilityHandler) renderSeedCSICompatibilityMode(values map[string]any) (*chartrenderer.RenderedChart, error) {
+ // TODO: constant
+ chartName := "stackit-blockstorage-csi-driver"
+
+ // Get the chart Values
+ csiStackitValues := values[openstack.CSISTACKITControllerName].(map[string]any)
+ // Merge csiStackitValues to topLevel. Basically removes the openstack.CSISTACKITControllerName key
+ chartValues := gardenerutils.MergeMaps(values, csiStackitValues)
+ // Override chart values
+ chartValues["prefix"] = "stackit-compat"
+
+ //TODO: Use gardener tools for this? If possible
+ imagesToFind := []string{
+ "csi-driver-stackit",
+ "csi-provisioner",
+ "csi-attacher",
+ "csi-snapshotter",
+ "csi-resizer",
+ "csi-liveness-probe",
+ "csi-snapshot-controller",
+ }
+ images := imagevector.ImageVector()
+ imageMap := make(map[string]any)
+
+ for _, image := range imagesToFind {
+ foundImage, err := images.FindImage(image)
+ if err != nil {
+ return nil, err
+ }
+ imageMap[image] = foundImage.String()
+ }
+ chartValues["images"] = imageMap
+
+ return ch.renderer.RenderEmbeddedFS(
+ charts.InternalChart,
+ filepath.Join(charts.InternalChartsPath, "seed-controlplane/charts/stackit-blockstorage-csi-driver"),
+ chartName,
+ "kube-system",
+ chartValues,
+ )
+}
+
+func (ch *CompatCSICompatibilityHandler) deploySeedCSICompatibilityMode(ctx context.Context, namespace string, renderedChart *chartrenderer.RenderedChart) error {
+ data := renderedChart.AsSecretData()
+ return managedresources.CreateForSeed(ctx, ch.client, namespace, "stackit-csi-compat-chart", false, data)
+}
+
+func (ch *CompatCSICompatibilityHandler) deleteSeedCSICompatibilityMode(ctx context.Context, namespace string) error {
+ return managedresources.DeleteForSeed(ctx, ch.client, namespace, "stackit-csi-compat-chart")
+}
+
+func (ch *CompatCSICompatibilityHandler) HandleShootCSICompatibility(ctx context.Context, namespace string, cpConfig *stackitv1alpha1.ControlPlaneConfig, values map[string]any) error {
+ compatibilityMode := getCSICompatibilityMode(cpConfig)
+ if compatibilityMode != stackitv1alpha1.DEFAULT {
+ blockLegacyCreation := compatibilityMode == stackitv1alpha1.COMPATBLOCK
+ chart, err := ch.renderShootCSICompatibilityMode(values, blockLegacyCreation)
+ if err != nil {
+ return fmt.Errorf("render shoot CSI compatibility mode: %w", err)
+ }
+ err = ch.deployShootCSICompatibilityMode(ctx, namespace, chart)
+ if err != nil {
+ return fmt.Errorf("deploy shoot CSI compatibility mode: %w", err)
+ }
+ } else {
+ err := ch.deleteShootCSICompatibilityMode(ctx, namespace)
+ if err != nil {
+ return fmt.Errorf("delete shoot CSI compatibility mode: %w", err)
+ }
+ }
+ return nil
+}
+
+func (ch *CompatCSICompatibilityHandler) renderShootCSICompatibilityMode(values map[string]any, blockLegacyCreation bool) (*chartrenderer.RenderedChart, error) {
+ // TODO: constant
+ chartName := "stackit-blockstorage-csi-driver"
+
+ // Get the chart Values
+ csiStackitValues := values[openstack.CSISTACKITControllerName].(map[string]any)
+ // Merge csiStackitValues to topLevel. Basically removes the openstack.CSISTACKITControllerName key
+ chartValues := gardenerutils.MergeMaps(values, csiStackitValues)
+ // Override chart values
+ chartValues["prefix"] = "stackit-compat"
+
+ //TODO: Use gardener tools for this? If possible
+ imagesToFind := []string{
+ "csi-driver-stackit",
+ "csi-node-driver-registrar",
+ "csi-liveness-probe",
+ }
+ images := imagevector.ImageVector()
+ imageMap := make(map[string]any)
+
+ for _, image := range imagesToFind {
+ foundImage, err := images.FindImage(image)
+ if err != nil {
+ return nil, err
+ }
+ imageMap[image] = foundImage.String()
+ }
+ chartValues["images"] = imageMap
+ chartValues["healthzPort"] = 9909
+ csiValues := map[string]any{
+ "enableCompatibilityMode": true,
+ }
+ if blockLegacyCreation {
+ csiValues["blockLegacyCreation"] = true
+ }
+ chartValues["csi"] = csiValues
+
+ return ch.renderer.RenderEmbeddedFS(
+ charts.InternalChart,
+ filepath.Join(charts.InternalChartsPath, "shoot-system-components/charts/stackit-blockstorage-csi-driver"),
+ chartName,
+ "kube-system",
+ chartValues,
+ )
+}
+
+func (ch *CompatCSICompatibilityHandler) deployShootCSICompatibilityMode(ctx context.Context, namespace string, renderedChart *chartrenderer.RenderedChart) error {
+ data := renderedChart.AsSecretData()
+ return managedresources.CreateForShoot(ctx, ch.client, namespace, "stackit-csi-compat-shoot-chart", "gardener-extension-provider-stackit", false, data)
+}
+
+func (ch *CompatCSICompatibilityHandler) deleteShootCSICompatibilityMode(ctx context.Context, namespace string) error {
+ return managedresources.DeleteForShoot(ctx, ch.client, namespace, "stackit-csi-compat-shoot-chart")
+}
diff --git a/pkg/controller/controlplane/csi_compatibility_test.go b/pkg/controller/controlplane/csi_compatibility_test.go
new file mode 100644
index 00000000..b8e68841
--- /dev/null
+++ b/pkg/controller/controlplane/csi_compatibility_test.go
@@ -0,0 +1,326 @@
+// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package controlplane
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+
+ resourcesv1alpha1 "github.com/gardener/gardener/pkg/apis/resources/v1alpha1"
+ "github.com/gardener/gardener/pkg/client/kubernetes"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/client-go/rest"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+ "sigs.k8s.io/yaml"
+
+ stackitv1alpha1 "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/v1alpha1"
+ "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/openstack"
+)
+
+type mockRoundTripper struct{}
+
+func (m *mockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+ var body string
+ switch req.URL.Path {
+ case "/version":
+ body = `{"major":"1","minor":"29","gitVersion":"v1.29.0"}`
+ case "/api":
+ body = `{"kind":"APIVersions","versions":["v1"]}`
+ case "/apis":
+ body = `{"kind":"APIGroupList","groups":[]}`
+ default:
+ body = `{"kind":"Status","status":"Failure","message":"Not Found","reason":"NotFound","code":404}`
+ }
+
+ return &http.Response{
+ StatusCode: http.StatusOK,
+ Header: http.Header{"Content-Type": []string{"application/json"}},
+ Body: io.NopCloser(bytes.NewReader([]byte(body))),
+ }, nil
+}
+
+var _ = Describe("CompatCSICompatibilityHandler", func() {
+ var (
+ ctx context.Context
+ fakeClient client.Client
+ handler *CompatCSICompatibilityHandler
+ namespace string
+ config *rest.Config
+ )
+
+ BeforeEach(func() {
+ ctx = context.Background()
+ namespace = "test-namespace"
+
+ fakeClient = fakeclient.NewClientBuilder().
+ WithScheme(kubernetes.SeedScheme).
+ Build()
+
+ config = &rest.Config{
+ Host: "https://localhost",
+ Transport: &mockRoundTripper{},
+ }
+
+ handler, _ = NewCompatCSICompatibilityHandler(fakeClient, config)
+ })
+
+ // getDaemonSetFromSecret := func(prefix string) *appsv1.DaemonSet {
+ // GinkgoHelper()
+ // secretList := &corev1.SecretList{}
+ // Expect(fakeClient.List(ctx, secretList, client.InNamespace(namespace))).To(Succeed())
+ // var matchedSecret *corev1.Secret
+ // var names []string
+ // for _, s := range secretList.Items {
+ // names = append(names, s.Name)
+ // if strings.HasPrefix(s.Name, prefix) {
+ // matchedSecret = &s
+ // break
+ // }
+ // }
+
+ // if matchedSecret == nil {
+ // Fail(fmt.Sprintf("Secret starting with prefix %s not found. Found secrets: %v", prefix, names))
+ // }
+
+ // for _, data := range matchedSecret.Data {
+ // docs := bytes.Split(data, []byte("\n---"))
+ // for _, doc := range docs {
+ // if bytes.Contains(doc, []byte("kind: DaemonSet")) {
+ // ds := &appsv1.DaemonSet{}
+ // Expect(yaml.Unmarshal(doc, ds)).To(Succeed())
+ // return ds
+ // }
+ // }
+ // }
+ // Fail("DaemonSet not found in secret " + matchedSecret.Name)
+ // return nil
+ // }
+
+ Describe("#HandleSeedCSICompatibility", func() {
+ Context("when CSICompatibilityMode is DEFAULT", func() {
+ It("should delete the managed resource", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ Name: string(stackitv1alpha1.DEFAULT),
+ },
+ },
+ }
+
+ // Create the managed resource and secret beforehand to ensure deletion works
+ mr := &resourcesv1alpha1.ManagedResource{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "stackit-csi-compat-chart",
+ Namespace: namespace,
+ },
+ }
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "managedresource-stackit-csi-compat-chart",
+ Namespace: namespace,
+ },
+ }
+ Expect(fakeClient.Create(ctx, mr)).To(Succeed())
+ Expect(fakeClient.Create(ctx, secret)).To(Succeed())
+
+ err := handler.HandleSeedCSICompatibility(ctx, namespace, cpConfig, nil)
+ Expect(err).NotTo(HaveOccurred())
+
+ // Check deletion
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: "stackit-csi-compat-chart", Namespace: namespace}, mr)
+ Expect(client.IgnoreNotFound(err)).To(Succeed())
+ Expect(err).ToNot(Succeed())
+ })
+ })
+
+ Context("when CSICompatibilityMode is COMPAT", func() {
+ It("should deploy the seed csi compatibility mode", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ Name: string(stackitv1alpha1.COMPAT),
+ },
+ },
+ }
+
+ controlPlaneValues := map[string]any{
+ "global": map[string]any{
+ "genericTokenKubeconfigSecretName": "generic-token-kubeconfig-92e9ae14",
+ },
+ openstack.CSISTACKITControllerName: map[string]any{
+ "foo": "bar",
+ },
+ }
+
+ err := handler.HandleSeedCSICompatibility(ctx, namespace, cpConfig, controlPlaneValues)
+ Expect(err).NotTo(HaveOccurred())
+
+ mr := &resourcesv1alpha1.ManagedResource{}
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: "stackit-csi-compat-chart", Namespace: namespace}, mr)
+ Expect(err).NotTo(HaveOccurred())
+ })
+ })
+ })
+
+ Describe("#HandleShootCSICompatibility", func() {
+ Context("when CSICompatibilityMode is DEFAULT", func() {
+ It("should delete the managed resource", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ Name: string(stackitv1alpha1.DEFAULT),
+ },
+ },
+ }
+
+ // Create the managed resource and secret beforehand to ensure deletion works
+ mr := &resourcesv1alpha1.ManagedResource{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "stackit-csi-compat-shoot-chart",
+ Namespace: namespace,
+ },
+ }
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "managedresource-stackit-csi-compat-shoot-chart",
+ Namespace: namespace,
+ },
+ }
+ Expect(fakeClient.Create(ctx, mr)).To(Succeed())
+ Expect(fakeClient.Create(ctx, secret)).To(Succeed())
+
+ err := handler.HandleShootCSICompatibility(ctx, namespace, cpConfig, nil)
+ Expect(err).NotTo(HaveOccurred())
+
+ // Check deletion
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: "stackit-csi-compat-shoot-chart", Namespace: namespace}, mr)
+ Expect(client.IgnoreNotFound(err)).To(Succeed())
+ Expect(err).ToNot(Succeed())
+ })
+ })
+
+ Context("when CSICompatibilityMode is COMPAT", func() {
+ It("should deploy the shoot csi compatibility mode with blockLegacyCreation = false", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ Name: string(stackitv1alpha1.COMPAT),
+ },
+ },
+ }
+
+ values := map[string]any{
+ "global": map[string]any{
+ "genericTokenKubeconfigSecretName": "generic-token-kubeconfig-92e9ae14",
+ },
+ openstack.CSISTACKITControllerName: map[string]any{
+ "foo": "bar",
+ },
+ }
+
+ err := handler.HandleShootCSICompatibility(ctx, namespace, cpConfig, values)
+ Expect(err).NotTo(HaveOccurred())
+
+ mr := &resourcesv1alpha1.ManagedResource{}
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: "stackit-csi-compat-shoot-chart", Namespace: namespace}, mr)
+ Expect(err).NotTo(HaveOccurred())
+
+ ds := getDaemonSetFromSecret(ctx, fakeClient, namespace, "managedresource-stackit-csi-compat-shoot-chart-")
+ var csiContainer *corev1.Container
+ for i := range ds.Spec.Template.Spec.Containers {
+ if ds.Spec.Template.Spec.Containers[i].Name == "csi-driver-stackit" {
+ csiContainer = &ds.Spec.Template.Spec.Containers[i]
+ break
+ }
+ }
+ Expect(csiContainer).NotTo(BeNil(), "csi-driver-stackit container not found")
+ Expect(csiContainer.Args).To(ContainElement("--legacy-storage-mode=true"))
+ Expect(csiContainer.Args).NotTo(ContainElement("--legacy-volume-creation=false"))
+ })
+ })
+
+ Context("when CSICompatibilityMode is COMPATBLOCK", func() {
+ It("should deploy the shoot csi compatibility mode with blockLegacyCreation = true", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ Name: string(stackitv1alpha1.COMPATBLOCK),
+ },
+ },
+ }
+
+ values := map[string]any{
+ "global": map[string]any{
+ "genericTokenKubeconfigSecretName": "generic-token-kubeconfig-92e9ae14",
+ },
+ openstack.CSISTACKITControllerName: map[string]any{
+ "foo": "bar",
+ },
+ }
+
+ err := handler.HandleShootCSICompatibility(ctx, namespace, cpConfig, values)
+ Expect(err).NotTo(HaveOccurred())
+
+ mr := &resourcesv1alpha1.ManagedResource{}
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: "stackit-csi-compat-shoot-chart", Namespace: namespace}, mr)
+ Expect(err).NotTo(HaveOccurred())
+
+ ds := getDaemonSetFromSecret(ctx, fakeClient, namespace, "managedresource-stackit-csi-compat-shoot-chart-")
+ var csiContainer *corev1.Container
+ for i := range ds.Spec.Template.Spec.Containers {
+ if ds.Spec.Template.Spec.Containers[i].Name == "csi-driver-stackit" {
+ csiContainer = &ds.Spec.Template.Spec.Containers[i]
+ break
+ }
+ }
+ Expect(csiContainer).NotTo(BeNil(), "csi-driver-stackit container not found")
+ Expect(csiContainer.Args).To(ContainElement("--legacy-storage-mode=true"))
+ Expect(csiContainer.Args).To(ContainElement("--legacy-volume-creation=false"))
+ })
+ })
+ })
+})
+
+func getDaemonSetFromSecret(ctx context.Context, fakeClient client.Client, namespace string, prefix string) *appsv1.DaemonSet {
+ GinkgoHelper()
+ secretList := &corev1.SecretList{}
+ Expect(fakeClient.List(ctx, secretList, client.InNamespace(namespace))).To(Succeed())
+ var matchedSecret *corev1.Secret
+ var names []string
+ for _, s := range secretList.Items {
+ names = append(names, s.Name)
+ if strings.HasPrefix(s.Name, prefix) {
+ matchedSecret = &s
+ break
+ }
+ }
+
+ if matchedSecret == nil {
+ Fail(fmt.Sprintf("Secret starting with prefix %s not found. Found secrets: %v", prefix, names))
+ }
+
+ for _, data := range matchedSecret.Data {
+ docs := bytes.Split(data, []byte("\n---"))
+ for _, doc := range docs {
+ if bytes.Contains(doc, []byte("kind: DaemonSet")) {
+ ds := &appsv1.DaemonSet{}
+ Expect(yaml.Unmarshal(doc, ds)).To(Succeed())
+ return ds
+ }
+ }
+ }
+ Fail("DaemonSet not found in secret " + matchedSecret.Name)
+ return nil
+}
diff --git a/pkg/controller/controlplane/valuesprovider.go b/pkg/controller/controlplane/valuesprovider.go
index 180ac234..86a2768f 100644
--- a/pkg/controller/controlplane/valuesprovider.go
+++ b/pkg/controller/controlplane/valuesprovider.go
@@ -43,6 +43,7 @@ import (
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
vpaautoscalingv1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
+ "k8s.io/client-go/rest"
"k8s.io/utils/ptr"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -355,13 +356,19 @@ var (
}
)
+type CSICompatibilityHandler interface {
+ HandleSeedCSICompatibility(context.Context, string, *stackitv1alpha1.ControlPlaneConfig, map[string]any) error
+ HandleShootCSICompatibility(context.Context, string, *stackitv1alpha1.ControlPlaneConfig, map[string]any) error
+}
+
// NewValuesProvider creates a new ValuesProvider for the generic actuator.
-func NewValuesProvider(mgr manager.Manager, deployALBIngressController bool, customLabelDomain string) genericactuator.ValuesProvider {
+func NewValuesProvider(mgr manager.Manager, deployALBIngressController bool, customLabelDomain string, csiCompatibilityHandler CSICompatibilityHandler) genericactuator.ValuesProvider {
return &valuesProvider{
client: mgr.GetClient(),
decoder: serializer.NewCodecFactory(mgr.GetScheme(), serializer.EnableStrict).UniversalDecoder(),
deployALBIngressController: deployALBIngressController,
customLabelDomain: customLabelDomain,
+ csiCompatibilityHandler: csiCompatibilityHandler,
}
}
@@ -369,9 +376,11 @@ func NewValuesProvider(mgr manager.Manager, deployALBIngressController bool, cus
type valuesProvider struct {
genericactuator.NoopValuesProvider
client k8sclient.Client
+ config *rest.Config
decoder runtime.Decoder
deployALBIngressController bool
customLabelDomain string
+ csiCompatibilityHandler CSICompatibilityHandler
}
// GetConfigChartValues returns the values for the config chart applied by the generic actuator.
@@ -736,6 +745,15 @@ func (vp *valuesProvider) getControlPlaneChartValues(ctx context.Context, cpConf
return nil, err
}
+ maps.Copy(controlPlaneValues, map[string]any{
+ "global": map[string]any{
+ "genericTokenKubeconfigSecretName": extensionscontroller.GenericTokenKubeconfigSecretNameFromCluster(cluster),
+ },
+ openstack.CloudControllerManagerName: ccm,
+ openstack.STACKITCloudControllerManagerName: stackitccm,
+ stackit.PodIdentityWebhookName: podIdentityWebhook,
+ })
+
storageCSIDriver := getCSIDriver(cpConfig)
switch storageCSIDriver {
case stackitv1alpha1.OPENSTACK:
@@ -750,19 +768,14 @@ func (vp *valuesProvider) getControlPlaneChartValues(ctx context.Context, cpConf
controlPlaneValues[openstack.CSIControllerName] = map[string]any{
"enabled": false,
}
+ err := vp.csiCompatibilityHandler.HandleSeedCSICompatibility(ctx, cp.Namespace, cpConfig, controlPlaneValues)
+ if err != nil {
+ return nil, err
+ }
default:
return nil, fmt.Errorf("unsupported storage CSI Driver: %s", storageCSIDriver)
}
- maps.Copy(controlPlaneValues, map[string]any{
- "global": map[string]any{
- "genericTokenKubeconfigSecretName": extensionscontroller.GenericTokenKubeconfigSecretNameFromCluster(cluster),
- },
- openstack.CloudControllerManagerName: ccm,
- openstack.STACKITCloudControllerManagerName: stackitccm,
- stackit.PodIdentityWebhookName: podIdentityWebhook,
- })
-
if vp.deployALBIngressController {
fmt.Println("deploying ALB Ingress Controller")
albcm, err := getSTACKITALBCMChartValues(cpConfig, cluster, infra, stackitCredentialsConfig, apiEndpoints, scaledDown, stackitRegion)
@@ -1070,12 +1083,16 @@ func (vp *valuesProvider) getControlPlaneShootChartValues(ctx context.Context, c
csiDriverInUse := getCSIDriver(cpConfig)
switch csiDriverInUse {
- case stackitv1alpha1.STACKIT:
- values[openstack.CSISTACKITNodeName] = csiDriverSTACKITValues
- values[openstack.CSINodeName] = map[string]any{"enabled": false}
case stackitv1alpha1.OPENSTACK:
values[openstack.CSINodeName] = csiNodeDriverValues
values[openstack.CSISTACKITNodeName] = map[string]any{"enabled": false}
+ case stackitv1alpha1.STACKIT:
+ values[openstack.CSISTACKITNodeName] = csiDriverSTACKITValues
+ values[openstack.CSINodeName] = map[string]any{"enabled": false}
+ err := vp.csiCompatibilityHandler.HandleShootCSICompatibility(ctx, cp.Namespace, cpConfig, values)
+ if err != nil {
+ return nil, err
+ }
default:
return nil, fmt.Errorf("unsupported CSI driver type: %s", csiDriverInUse)
}
@@ -1216,6 +1233,10 @@ func getCSIDriver(cpConfig *stackitv1alpha1.ControlPlaneConfig) stackitv1alpha1.
return stackitv1alpha1.ControllerName(cpConfig.Storage.CSI.Name)
}
+func getCSICompatibilityMode(cpConfig *stackitv1alpha1.ControlPlaneConfig) stackitv1alpha1.CSICompatibilityMode {
+ return stackitv1alpha1.CSICompatibilityMode(cpConfig.Storage.CSI.CompatibilityMode)
+}
+
func getCCMController(cpConfig *stackitv1alpha1.ControlPlaneConfig) stackitv1alpha1.ControllerName {
return stackitv1alpha1.ControllerName(cpConfig.CloudControllerManager.Name)
}
diff --git a/pkg/controller/controlplane/valuesprovider_test.go b/pkg/controller/controlplane/valuesprovider_test.go
index d7da5fb4..ac14253f 100644
--- a/pkg/controller/controlplane/valuesprovider_test.go
+++ b/pkg/controller/controlplane/valuesprovider_test.go
@@ -313,7 +313,7 @@ var _ = Describe("ValuesProvider", func() {
mgr = &testutils.FakeManager{Scheme: scheme, Client: c}
- vp = NewValuesProvider(mgr, true, "kubernetes.io")
+ vp = NewValuesProvider(mgr, true, "kubernetes.io", new(noopCSICompatibilityHandler))
})
AfterEach(func() {
@@ -665,7 +665,7 @@ var _ = Describe("ValuesProvider", func() {
stackitCCMDeletion(ctx, c)
}
- vpStackitConf := NewValuesProvider(mgr, true, "kubernetes.io")
+ vpStackitConf := NewValuesProvider(mgr, true, "kubernetes.io", new(noopCSICompatibilityHandler))
values, err := vpStackitConf.GetControlPlaneChartValues(ctx, cp, &testCluster, fakeSecretsManager, checksums, false)
Expect(err).NotTo(HaveOccurred())
Expect(values).To(HaveKey(openstack.STACKITCloudControllerManagerName))
@@ -777,7 +777,7 @@ var _ = Describe("ValuesProvider", func() {
}
testCluster.CloudProfile = cloudProfile
- vpCustomDomain := NewValuesProvider(mgr, true, customDomain)
+ vpCustomDomain := NewValuesProvider(mgr, true, customDomain, new(noopCSICompatibilityHandler))
values, err := vpCustomDomain.GetControlPlaneChartValues(ctx, cp, &testCluster, fakeSecretsManager, checksums, false)
Expect(err).NotTo(HaveOccurred())
@@ -1142,3 +1142,12 @@ func stackitCCMDeletion(ctx context.Context, c *mockclient.MockClient) {
c.EXPECT().Delete(ctx, &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: openstack.STACKITCloudControllerManagerName, Namespace: namespace}})
c.EXPECT().Delete(ctx, &vpaautoscalingv1.VerticalPodAutoscaler{ObjectMeta: metav1.ObjectMeta{Name: openstack.STACKITCloudControllerManagerName + "-vpa", Namespace: namespace}})
}
+
+type noopCSICompatibilityHandler struct{}
+
+func (*noopCSICompatibilityHandler) HandleSeedCSICompatibility(context.Context, string, *stackitv1alpha1.ControlPlaneConfig, map[string]any) error {
+ return nil
+}
+func (*noopCSICompatibilityHandler) HandleShootCSICompatibility(context.Context, string, *stackitv1alpha1.ControlPlaneConfig, map[string]any) error {
+ return nil
+}