diff --git a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/csi-snapshot-controller-poddisruptionbudget.yaml b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/csi-snapshot-controller-poddisruptionbudget.yaml
index 3b1f29ad..e8de9025 100644
--- a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/csi-snapshot-controller-poddisruptionbudget.yaml
+++ b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/csi-snapshot-controller-poddisruptionbudget.yaml
@@ -1,3 +1,4 @@
+{{- if not .Values.csi.enableCompatibilityMode }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
@@ -13,3 +14,4 @@ spec:
app: {{.Values.prefix}}-csi-snapshot-controller
role: controller
unhealthyPodEvictionPolicy: AlwaysAllow
+{{- end }}
diff --git a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/csi-snapshot-controller-vpa.yaml b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/csi-snapshot-controller-vpa.yaml
index bbe8278f..fd4164ec 100644
--- a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/csi-snapshot-controller-vpa.yaml
+++ b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/csi-snapshot-controller-vpa.yaml
@@ -1,4 +1,4 @@
----
+{{- if not .Values.csi.enableCompatibilityMode }}
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
@@ -15,3 +15,4 @@ spec:
name: {{.Values.prefix}}-csi-snapshot-controller
updatePolicy:
updateMode: Auto
+{{- end }}
diff --git a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-driver-controller.yaml b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-driver-controller.yaml
index 391bf0f5..32f20b73 100644
--- a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-driver-controller.yaml
+++ b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-driver-controller.yaml
@@ -52,6 +52,12 @@ spec:
{{- end }}
- --v=3
- --provide-node-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 }}/csi.sock
@@ -69,7 +75,7 @@ spec:
{{- end }}
ports:
- name: healthz
- containerPort: 9808
+ containerPort: {{ .Values.healthzPort }}
protocol: TCP
livenessProbe:
httpGet:
@@ -251,6 +257,7 @@ spec:
args:
- --probe-timeout=3m
- --csi-address=/csi/csi.sock
+ - --health-port={{.Values.healthzPort}}
{{- if .Values.resources.livenessProbe }}
resources:
{{ toYaml .Values.resources.livenessProbe | indent 10 }}
diff --git a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-snapshot-controller.yaml b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-snapshot-controller.yaml
index 66c4daf0..f3e26cfc 100644
--- a/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-snapshot-controller.yaml
+++ b/charts/internal/seed-controlplane/charts/stackit-blockstorage-csi-driver/templates/deployment-csi-snapshot-controller.yaml
@@ -1,3 +1,4 @@
+{{- if not .Values.csi.enableCompatibilityMode }}
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -70,3 +71,4 @@ spec:
path: token
name: shoot-access-csi-snapshot-controller
optional: false
+{{- end }}
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..cf5a4243 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
@@ -4,6 +4,12 @@ kubernetesVersion: 1.30.0
prefix: stackit-blockstorage
projectID: ""
+healthzPort: 9808
+
+csi:
+ enableCompatibilityMode: false
+ blockLegacyCreation: false
+
images:
csi-driver-stackit: image-repository:image-tag
csi-provisioner: image-repository:image-tag
@@ -12,7 +18,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..273f2941 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
@@ -17,7 +17,11 @@ spec:
metadata:
annotations:
checksum/configmap-cloud-provider-config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
- node.gardener.cloud/wait-for-csi-node-stackit: block-storage.csi.stackit.cloud
+ {{- if eq .Values.driverName "cinder.csi.openstack.org" }}
+ node.gardener.cloud/wait-for-csi-node-openstack: {{ .Values.driverName }}
+ {{- else }}
+ node.gardener.cloud/wait-for-csi-node-stackit: {{ .Values.driverName }}
+ {{- end }}
labels:
node.gardener.cloud/critical-component: "true"
app: {{ .Values.prefix }}-csi
@@ -48,6 +52,9 @@ spec:
{{- end }}
- --v=2
- --provide-controller-service=false
+ {{- if .Values.csi.enableCompatibilityMode }}
+ - --legacy-storage-mode=true
+ {{- end }}
env:
- name: CSI_ENDPOINT
value: unix://{{ .Values.socketPath }}
@@ -62,7 +69,7 @@ spec:
allowPrivilegeEscalation: true
ports:
- name: healthz
- containerPort: 9908
+ containerPort: {{ .Values.healthzPort }}
protocol: TCP
livenessProbe:
httpGet:
@@ -94,7 +101,7 @@ spec:
- name: ADDRESS
value: {{ .Values.socketPath }}
- name: DRIVER_REG_SOCK_PATH
- value: /var/lib/kubelet/plugins/block-storage.csi.stackit.cloud/csi.sock
+ value: /var/lib/kubelet/plugins/{{ .Values.driverName }}/csi.sock
{{- if .Values.resources.nodeDriverRegistrar }}
resources:
{{ toYaml .Values.resources.nodeDriverRegistrar | indent 10 }}
@@ -112,7 +119,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 }}
@@ -130,7 +137,7 @@ spec:
type: Directory
- name: plugin-dir
hostPath:
- path: /var/lib/kubelet/plugins/block-storage.csi.stackit.cloud/
+ path: /var/lib/kubelet/plugins/{{ .Values.driverName }}/
type: DirectoryOrCreate
- name: registration-dir
hostPath:
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..89178b01 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,11 @@ driverName: block-storage.csi.stackit.cloud
rescanBlockStorageOnResize: "true"
+healthzPort: 9908
+
+csi:
+ enableCompatibilityMode: false
+
images:
csi-driver-stackit: image-repository:image-tag
csi-node-driver-registrar: image-repository:image-tag
diff --git a/go.mod b/go.mod
index 53d65ee1..c132ec7f 100644
--- a/go.mod
+++ b/go.mod
@@ -45,6 +45,7 @@ require (
k8s.io/utils v0.0.0-20260507154919-ff6756f316d2
sigs.k8s.io/controller-runtime v0.23.3
sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20231015215740-bf15e44028f9
+ sigs.k8s.io/yaml v1.6.0
)
require (
@@ -53,6 +54,7 @@ require (
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
+ github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/PaesslerAG/gval v1.2.4 // indirect
github.com/PaesslerAG/jsonpath v0.1.2-0.20240726212847-3a740cf7976f // indirect
github.com/VictoriaMetrics/VictoriaLogs v1.36.2-0.20251008164716-21c0fb3de84d // indirect
@@ -83,6 +85,7 @@ require (
github.com/brunoga/deep v1.3.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/coreos/go-semver v0.3.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/elastic/crd-ref-docs v0.3.0 // indirect
@@ -90,6 +93,7 @@ require (
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fatih/color v1.19.0 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fluent/fluent-operator/v3 v3.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
@@ -113,6 +117,7 @@ require (
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
+ github.com/go-test/deep v1.1.1 // indirect
github.com/gobuffalo/flect v1.0.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
@@ -125,6 +130,7 @@ require (
github.com/google/gnostic-models v0.7.1 // indirect
github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
+ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -134,6 +140,7 @@ require (
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 // indirect
+ github.com/kylelemons/godebug v1.1.0 // indirect
github.com/labstack/echo/v4 v4.15.1 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
@@ -160,6 +167,7 @@ require (
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/prometheus/sigv4 v0.3.0 // indirect
+ github.com/robfig/cron v1.2.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
@@ -175,7 +183,12 @@ require (
github.com/x448/float16 v0.8.4 // indirect
github.com/zitadel/oidc/v3 v3.45.4 // indirect
github.com/zitadel/schema v1.3.2 // indirect
+ go.etcd.io/etcd/api/v3 v3.6.5 // indirect
+ go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
+ go.etcd.io/etcd/client/v3 v3.6.5 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/contrib/otelconf v0.23.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect
@@ -218,6 +231,7 @@ require (
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
helm.sh/helm/v4 v4.1.4 // indirect
@@ -228,14 +242,15 @@ require (
k8s.io/component-helpers v0.35.5 // indirect
k8s.io/gengo/v2 v2.0.0-20251215205346-5ee0d033ba5b // indirect
k8s.io/klog/v2 v2.140.0 // indirect
+ k8s.io/kms v0.35.5 // indirect
k8s.io/kube-aggregator v0.35.5 // indirect
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
k8s.io/metrics v0.35.5 // indirect
k8s.io/pod-security-admission v0.35.5 // indirect
+ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect
sigs.k8s.io/controller-tools v0.20.1 // indirect
sigs.k8s.io/gateway-api v1.5.0 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
- sigs.k8s.io/yaml v1.6.0 // indirect
)
diff --git a/go.sum b/go.sum
index 9fba0ce7..4bc2b3e0 100644
--- a/go.sum
+++ b/go.sum
@@ -39,6 +39,8 @@ github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
+github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/PaesslerAG/gval v1.2.2/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac=
github.com/PaesslerAG/gval v1.2.4 h1:rhX7MpjJlcxYwL2eTTYIOBUyEKZ+A96T9vQySWkVUiU=
github.com/PaesslerAG/gval v1.2.4/go.mod h1:XRFLwvmkTEdYziLdaCeCa5ImcGVrfQbeNUbVR+C6xac=
@@ -112,6 +114,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
+github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -126,6 +130,8 @@ github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxK
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elastic/crd-ref-docs v0.3.0 h1:9bGSUkBR56Z7TuDGQAu3KGbBkagwwZ6RkZmS+qvDuDM=
github.com/elastic/crd-ref-docs v0.3.0/go.mod h1:8td3UC8CaO5M+G115O3FRKLmplmX+p0EqLMLGM6uNdk=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
@@ -238,6 +244,8 @@ github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvA
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
+github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
+github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@@ -311,6 +319,12 @@ github.com/gophercloud/utils/v2 v2.0.0-20260424064311-2eeed4ceb3e9/go.mod h1:bIE
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
+github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
+github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -327,6 +341,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
+github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
@@ -442,6 +458,8 @@ github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEy
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/prometheus/sigv4 v0.3.0 h1:QIG7nTbu0JTnNidGI1Uwl5AGVIChWUACxn2B/BQ1kms=
github.com/prometheus/sigv4 v0.3.0/go.mod h1:fKtFYDus2M43CWKMNtGvFNHGXnAJJEGZbiYCmVp/F8I=
+github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
@@ -453,6 +471,8 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
+github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
+github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
@@ -500,6 +520,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
@@ -516,6 +538,8 @@ github.com/valyala/quicktemplate v1.8.0 h1:zU0tjbIqTRgKQzFY1L42zq0qR3eh4WoQQdIdq
github.com/valyala/quicktemplate v1.8.0/go.mod h1:qIqW8/igXt8fdrUln5kOSb+KWMaJ4Y8QUsfd1k6L2jM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
+github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
+github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -524,11 +548,27 @@ github.com/zitadel/oidc/v3 v3.45.4 h1:GKyWaPRVQ8sCu9XgJ3NgNGtG52FzwVJpzXjIUG2+Yr
github.com/zitadel/oidc/v3 v3.45.4/go.mod h1:XALmFXS9/kSom9B6uWin1yJ2WTI/E4Ti5aXJdewAVEs=
github.com/zitadel/schema v1.3.2 h1:gfJvt7dOMfTmxzhscZ9KkapKo3Nei3B6cAxjav+lyjI=
github.com/zitadel/schema v1.3.2/go.mod h1:IZmdfF9Wu62Zu6tJJTH3UsArevs3Y4smfJIj3L8fzxw=
+go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
+go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
+go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
+go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
+go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
+go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
+go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
+go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
+go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM=
+go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU=
+go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0=
+go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0=
+go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
+go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/contrib/otelconf v0.23.0 h1:s3C7KdMYiutf4rC8hKFA0WOIDG+gIru8ajjQKS59ir8=
@@ -786,6 +826,8 @@ gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWM
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -839,6 +881,8 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
+k8s.io/kms v0.35.5 h1:KTbr4tWIl7+OeYcY30Vwzv0G0Pz8EeSWcAnx5P7gBPY=
+k8s.io/kms v0.35.5/go.mod h1:c/uQe/eKrWdBkvizLFW+ThLA6tTzR0RkkwJJyzDRT1g=
k8s.io/kube-aggregator v0.35.5 h1:oLflHAqh8tEoEcXtrzGhr4hctwhcRr5B1sM+T96N1rs=
k8s.io/kube-aggregator v0.35.5/go.mod h1:L3GflyN8a8CDjej2UxgeGwRiXYuI+aTZ0GE7qssdN2w=
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
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..db59f27a
--- /dev/null
+++ b/pkg/controller/controlplane/csi_compatibility.go
@@ -0,0 +1,187 @@
+package controlplane
+
+import (
+ "context"
+ "fmt"
+ "path/filepath"
+
+ "github.com/gardener/gardener/pkg/chartrenderer"
+ gardenerutils "github.com/gardener/gardener/pkg/utils"
+ imagevectorutils "github.com/gardener/gardener/pkg/utils/imagevector"
+ "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"
+)
+
+const (
+ csiDriverChartName = "stackit-blockstorage-csi-driver"
+ csiCompatibilityPrefix = "stackit-compatibility"
+ csiCompatSeedChartName = csiCompatibilityPrefix + "-chart"
+ csiCompatShootChartName = csiCompatibilityPrefix + "-shoot-chart"
+)
+
+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, version string, cpConfig *stackitv1alpha1.ControlPlaneConfig, controlPlaneValues map[string]any) error {
+ compatibilityMode := getCSICompatibilityMode(cpConfig)
+ switch compatibilityMode {
+ case stackitv1alpha1.COMPAT, stackitv1alpha1.COMPATBLOCK:
+ blockLegacyCreation := compatibilityMode == stackitv1alpha1.COMPATBLOCK
+ chart, err := ch.renderSeedCSICompatibilityMode(controlPlaneValues, namespace, version, blockLegacyCreation)
+ 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)
+ }
+ default:
+ 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, namespace string, version string, blockLegacyCreation bool) (*chartrenderer.RenderedChart, error) {
+ chartValues := composeCompatibilityChartValues(values)
+
+ // Override chart values
+ chartValues["prefix"] = csiCompatibilityPrefix
+
+ imageMap, err := findImages(version, "csi-driver-stackit", "csi-provisioner", "csi-attacher", "csi-snapshotter", "csi-resizer", "csi-liveness-probe", "csi-snapshot-controller")
+ if err != nil {
+ return nil, err
+ }
+ chartValues["images"] = imageMap
+
+ chartValues["healthzPort"] = 9809
+ csiValues := map[string]any{
+ "enableCompatibilityMode": true,
+ }
+ if blockLegacyCreation {
+ csiValues["blockLegacyCreation"] = true
+ }
+ chartValues["csi"] = csiValues
+
+ return ch.renderer.RenderEmbeddedFS(
+ charts.InternalChart,
+ filepath.Join(charts.InternalChartsPath, "seed-controlplane/charts/stackit-blockstorage-csi-driver"),
+ csiDriverChartName,
+ namespace,
+ chartValues,
+ )
+}
+
+func (ch *CompatCSICompatibilityHandler) deploySeedCSICompatibilityMode(ctx context.Context, namespace string, renderedChart *chartrenderer.RenderedChart) error {
+ data := renderedChart.AsSecretData()
+ return managedresources.CreateForSeed(ctx, ch.client, namespace, csiCompatSeedChartName, false, data)
+}
+
+func (ch *CompatCSICompatibilityHandler) deleteSeedCSICompatibilityMode(ctx context.Context, namespace string) error {
+ return client.IgnoreNotFound(managedresources.DeleteForSeed(ctx, ch.client, namespace, csiCompatSeedChartName))
+}
+
+func (ch *CompatCSICompatibilityHandler) HandleShootCSICompatibility(ctx context.Context, namespace string, version string, cpConfig *stackitv1alpha1.ControlPlaneConfig, values map[string]any) error {
+ compatibilityMode := getCSICompatibilityMode(cpConfig)
+ switch compatibilityMode {
+ case stackitv1alpha1.COMPAT, stackitv1alpha1.COMPATBLOCK:
+ chart, err := ch.renderShootCSICompatibilityMode(values, version)
+ 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)
+ }
+ default:
+ 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, version string) (*chartrenderer.RenderedChart, error) {
+ chartValues := composeCompatibilityChartValues(values)
+
+ // Override chart values
+ chartValues["prefix"] = csiCompatibilityPrefix
+
+ imageMap, err := findImages(version, "csi-driver-stackit", "csi-node-driver-registrar", "csi-liveness-probe")
+ if err != nil {
+ return nil, err
+ }
+ chartValues["images"] = imageMap
+
+ chartValues["healthzPort"] = 9909
+ chartValues["driverName"] = openstack.CSIStorageProvisioner
+ csiValues := map[string]any{
+ "enableCompatibilityMode": true,
+ }
+ chartValues["csi"] = csiValues
+
+ return ch.renderer.RenderEmbeddedFS(
+ charts.InternalChart,
+ filepath.Join(charts.InternalChartsPath, "shoot-system-components/charts/stackit-blockstorage-csi-driver"),
+ csiDriverChartName,
+ "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, csiCompatShootChartName, "gardener-extension-provider-stackit", false, data)
+}
+
+func (ch *CompatCSICompatibilityHandler) deleteShootCSICompatibilityMode(ctx context.Context, namespace string) error {
+ return client.IgnoreNotFound(managedresources.DeleteForShoot(ctx, ch.client, namespace, csiCompatShootChartName))
+}
+
+// composeCompatibilityChartValues returns a copy of the given values map merged with the csiStackitValues on topLevel.
+// Basically removes the openstack.CSISTACKITControllerName key
+func composeCompatibilityChartValues(values map[string]any) map[string]any {
+ if values == nil {
+ return map[string]any{}
+ }
+ csiStackitValues, ok := values[openstack.CSISTACKITControllerName].(map[string]any)
+ if !ok {
+ csiStackitValues = nil
+ }
+ return gardenerutils.MergeMaps(values, csiStackitValues)
+}
+
+func findImages(version string, imagesToFind ...string) (map[string]any, error) {
+ images := imagevector.ImageVector()
+ result := make(map[string]any)
+ for _, image := range imagesToFind {
+ foundImage, err := images.FindImage(image, imagevectorutils.TargetVersion(version))
+ if err != nil {
+ return nil, err
+ }
+ result[image] = foundImage.String()
+ }
+ return result, nil
+}
diff --git a/pkg/controller/controlplane/csi_compatibility_test.go b/pkg/controller/controlplane/csi_compatibility_test.go
new file mode 100644
index 00000000..253d6adb
--- /dev/null
+++ b/pkg/controller/controlplane/csi_compatibility_test.go
@@ -0,0 +1,474 @@
+// 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"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ 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
+ shootVersion string
+ controlPlaneValues map[string]any
+ )
+
+ BeforeEach(func() {
+ ctx = context.Background()
+ namespace = "test-namespace"
+ shootVersion = "1.33.5"
+
+ fakeClient = fakeclient.NewClientBuilder().
+ WithScheme(kubernetes.SeedScheme).
+ Build()
+
+ config = &rest.Config{
+ Host: "https://localhost",
+ Transport: &mockRoundTripper{},
+ }
+
+ handler, _ = NewCompatCSICompatibilityHandler(fakeClient, config)
+
+ controlPlaneValues = map[string]any{
+ "global": map[string]any{
+ "genericTokenKubeconfigSecretName": "generic-token-kubeconfig-92e9ae14",
+ },
+ openstack.CSISTACKITControllerName: map[string]any{
+ "foo": "bar",
+ },
+ }
+ })
+
+ 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{
+ CompatibilityMode: string(stackitv1alpha1.DEFAULT),
+ },
+ },
+ }
+
+ // Create the managed resource and secret beforehand to ensure deletion works
+ mr := &resourcesv1alpha1.ManagedResource{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: csiCompatSeedChartName,
+ Namespace: namespace,
+ },
+ }
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "managedresource-" + csiCompatSeedChartName,
+ Namespace: namespace,
+ },
+ }
+ Expect(fakeClient.Create(ctx, mr)).To(Succeed())
+ Expect(fakeClient.Create(ctx, secret)).To(Succeed())
+
+ err := handler.HandleSeedCSICompatibility(ctx, namespace, shootVersion, cpConfig, controlPlaneValues)
+ Expect(err).NotTo(HaveOccurred())
+
+ // Check deletion
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: csiCompatSeedChartName, Namespace: namespace}, mr)
+ Expect(err).To(HaveOccurred())
+ Expect(apierrors.IsNotFound(err)).To(BeTrue())
+ })
+ })
+
+ Context("when CSICompatibilityMode is not set", func() {
+ It("should delete the managed resource", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ CompatibilityMode: "",
+ },
+ },
+ }
+
+ // Create the managed resource and secret beforehand to ensure deletion works
+ mr := &resourcesv1alpha1.ManagedResource{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: csiCompatSeedChartName,
+ Namespace: namespace,
+ },
+ }
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "managedresource-" + csiCompatSeedChartName,
+ Namespace: namespace,
+ },
+ }
+ Expect(fakeClient.Create(ctx, mr)).To(Succeed())
+ Expect(fakeClient.Create(ctx, secret)).To(Succeed())
+
+ err := handler.HandleSeedCSICompatibility(ctx, namespace, shootVersion, cpConfig, controlPlaneValues)
+ Expect(err).NotTo(HaveOccurred())
+
+ // Check deletion
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: csiCompatSeedChartName, Namespace: namespace}, mr)
+ Expect(err).To(HaveOccurred())
+ Expect(apierrors.IsNotFound(err)).To(BeTrue())
+ })
+ })
+
+ Context("when CSICompatibilityMode is COMPAT", func() {
+ It("should deploy the seed csi compatibility mode", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ CompatibilityMode: string(stackitv1alpha1.COMPAT),
+ },
+ },
+ }
+
+ err := handler.HandleSeedCSICompatibility(ctx, namespace, shootVersion, cpConfig, controlPlaneValues)
+ Expect(err).NotTo(HaveOccurred())
+
+ mr := &resourcesv1alpha1.ManagedResource{}
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: "stackit-compatibility-chart", Namespace: namespace}, mr)
+ Expect(err).NotTo(HaveOccurred())
+
+ deployment := getDeploymentFromSecret(ctx, fakeClient, namespace, "managedresource-"+csiCompatSeedChartName, "stackit-compatibility-csi-driver-controller")
+ csiContainer, found := findContainerInPod(&deployment.Spec.Template.Spec, "stackit-csi-driver")
+ Expect(found).To(BeTrue(), "stackit-csi-driver container not found")
+ Expect(csiContainer.Args).To(ContainElement("--legacy-storage-mode=true"))
+ Expect(csiContainer.Args).NotTo(ContainElement("--legacy-volume-creation=false"))
+
+ livenessContainer, found := findContainerInPod(&deployment.Spec.Template.Spec, "stackit-csi-liveness-probe")
+ Expect(found).To(BeTrue(), "stackit-csi-liveness-probe container not found")
+ Expect(livenessContainer.Args).To(ContainElement("--health-port=9809"))
+
+ Expect(deploymentExistsInSecret(ctx, fakeClient, namespace, "managedresource-"+csiCompatSeedChartName, "stackit-compatibility-csi-snapshot-controller")).To(BeFalse())
+ })
+ })
+
+ Context("when CSICompatibilityMode is COMPATBLOCK", func() {
+ It("should deploy the seed csi compatibility mode", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ CompatibilityMode: string(stackitv1alpha1.COMPATBLOCK),
+ },
+ },
+ }
+
+ err := handler.HandleSeedCSICompatibility(ctx, namespace, shootVersion, cpConfig, controlPlaneValues)
+ Expect(err).NotTo(HaveOccurred())
+
+ mr := &resourcesv1alpha1.ManagedResource{}
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: "stackit-compatibility-chart", Namespace: namespace}, mr)
+ Expect(err).NotTo(HaveOccurred())
+
+ deployment := getDeploymentFromSecret(ctx, fakeClient, namespace, "managedresource-"+csiCompatSeedChartName, "stackit-compatibility-csi-driver-controller")
+
+ csiContainer, found := findContainerInPod(&deployment.Spec.Template.Spec, "stackit-csi-driver")
+ Expect(found).To(BeTrue(), "stackit-csi-driver container not found")
+ Expect(csiContainer.Args).To(ContainElement("--legacy-storage-mode=true"))
+ Expect(csiContainer.Args).To(ContainElement("--legacy-volume-creation=false"))
+
+ livenessContainer, found := findContainerInPod(&deployment.Spec.Template.Spec, "stackit-csi-liveness-probe")
+ Expect(found).To(BeTrue(), "stackit-csi-liveness-probe container not found")
+ Expect(livenessContainer.Args).To(ContainElement("--health-port=9809"))
+
+ Expect(deploymentExistsInSecret(ctx, fakeClient, namespace, "managedresource-"+csiCompatSeedChartName, "stackit-compatibility-csi-snapshot-controller")).To(BeFalse())
+ })
+ })
+ })
+
+ 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{
+ CompatibilityMode: string(stackitv1alpha1.DEFAULT),
+ },
+ },
+ }
+
+ // Create the managed resource and secret beforehand to ensure deletion works
+ mr := &resourcesv1alpha1.ManagedResource{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: csiCompatShootChartName,
+ Namespace: namespace,
+ },
+ }
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "managedresource-" + csiCompatShootChartName,
+ Namespace: namespace,
+ },
+ }
+ Expect(fakeClient.Create(ctx, mr)).To(Succeed())
+ Expect(fakeClient.Create(ctx, secret)).To(Succeed())
+
+ err := handler.HandleShootCSICompatibility(ctx, namespace, shootVersion, cpConfig, controlPlaneValues)
+ Expect(err).NotTo(HaveOccurred())
+
+ // Check deletion
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: csiCompatShootChartName, Namespace: namespace}, mr)
+ Expect(apierrors.IsNotFound(err)).To(BeTrue())
+ })
+ })
+
+ Context("when CSICompatibilityMode is not set", func() {
+ It("should delete the managed resource", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ CompatibilityMode: "",
+ },
+ },
+ }
+
+ // Create the managed resource and secret beforehand to ensure deletion works
+ mr := &resourcesv1alpha1.ManagedResource{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: csiCompatShootChartName,
+ Namespace: namespace,
+ },
+ }
+ secret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "managedresource-" + csiCompatShootChartName,
+ Namespace: namespace,
+ },
+ }
+ Expect(fakeClient.Create(ctx, mr)).To(Succeed())
+ Expect(fakeClient.Create(ctx, secret)).To(Succeed())
+
+ err := handler.HandleShootCSICompatibility(ctx, namespace, shootVersion, cpConfig, controlPlaneValues)
+ Expect(err).NotTo(HaveOccurred())
+
+ // Check deletion
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: csiCompatShootChartName, Namespace: namespace}, mr)
+ Expect(apierrors.IsNotFound(err)).To(BeTrue())
+ })
+ })
+
+ 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{
+ CompatibilityMode: string(stackitv1alpha1.COMPAT),
+ },
+ },
+ }
+
+ err := handler.HandleShootCSICompatibility(ctx, namespace, shootVersion, cpConfig, controlPlaneValues)
+ Expect(err).NotTo(HaveOccurred())
+
+ mr := &resourcesv1alpha1.ManagedResource{}
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: csiCompatShootChartName, Namespace: namespace}, mr)
+ Expect(err).NotTo(HaveOccurred())
+
+ ds := getDaemonSetFromSecret(ctx, fakeClient, namespace, "managedresource-"+csiCompatShootChartName)
+
+ csiContainer, found := findContainerInPod(&ds.Spec.Template.Spec, "csi-driver-stackit")
+ Expect(found).To(BeTrue(), "csi-driver-stackit container not found")
+ Expect(csiContainer.Args).To(ContainElement("--legacy-storage-mode=true"))
+ Expect(csiContainer.Args).NotTo(ContainElement("--legacy-volume-creation=false"))
+
+ livenessContainer, found := findContainerInPod(&ds.Spec.Template.Spec, "csi-liveness-probe")
+ Expect(found).To(BeTrue(), "csi-liveness-probe container not found")
+ Expect(livenessContainer.Args).To(ContainElement("--health-port=9909"))
+
+ Expect(ds.Spec.Template.Annotations).To(HaveKeyWithValue("node.gardener.cloud/wait-for-csi-node-openstack", openstack.CSIStorageProvisioner))
+ Expect(ds.Spec.Template.Annotations).NotTo(HaveKey("node.gardener.cloud/wait-for-csi-node-stackit"))
+ })
+ })
+
+ 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{
+ CompatibilityMode: 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, shootVersion, cpConfig, values)
+ Expect(err).NotTo(HaveOccurred())
+
+ mr := &resourcesv1alpha1.ManagedResource{}
+ err = fakeClient.Get(ctx, types.NamespacedName{Name: csiCompatShootChartName, Namespace: namespace}, mr)
+ Expect(err).NotTo(HaveOccurred())
+
+ ds := getDaemonSetFromSecret(ctx, fakeClient, namespace, "managedresource-"+csiCompatShootChartName)
+
+ csiContainer, found := findContainerInPod(&ds.Spec.Template.Spec, "csi-driver-stackit")
+ Expect(found).To(BeTrue(), "csi-driver-stackit container not found")
+ Expect(csiContainer.Args).To(ContainElement("--legacy-storage-mode=true"))
+
+ livenessContainer, found := findContainerInPod(&ds.Spec.Template.Spec, "csi-liveness-probe")
+ Expect(found).To(BeTrue(), "csi-liveness-probe container not found")
+ Expect(livenessContainer.Args).To(ContainElement("--health-port=9909"))
+
+ Expect(ds.Spec.Template.Annotations).To(HaveKeyWithValue("node.gardener.cloud/wait-for-csi-node-openstack", openstack.CSIStorageProvisioner))
+ Expect(ds.Spec.Template.Annotations).NotTo(HaveKey("node.gardener.cloud/wait-for-csi-node-stackit"))
+ })
+ })
+ })
+})
+
+func getDaemonSetFromSecret(ctx context.Context, fakeClient client.Client, namespace string, secretNamePrefix 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, secretNamePrefix) {
+ matchedSecret = &s
+ break
+ }
+ }
+
+ if matchedSecret == nil {
+ Fail(fmt.Sprintf("Secret starting with prefix %s not found. Found secrets: %v", secretNamePrefix, names))
+ return nil // will never be reached, but makes the linter very happy
+ }
+
+ for _, data := range matchedSecret.Data {
+ docs := bytes.SplitSeq(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
+}
+
+func getDeploymentFromSecret(ctx context.Context, fakeClient client.Client, namespace string, secretName string, deploymentName string) *appsv1.Deployment {
+ 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, secretName) {
+ matchedSecret = &s
+ break
+ }
+ }
+
+ if matchedSecret == nil {
+ Fail(fmt.Sprintf("Secret starting with prefix %s not found. Found secrets: %v", secretName, names))
+ return nil // will never be reached, but makes the linter very happy
+ }
+
+ for _, data := range matchedSecret.Data {
+ docs := bytes.SplitSeq(data, []byte("\n---"))
+ for doc := range docs {
+ if bytes.Contains(doc, []byte("kind: Deployment")) {
+ deployment := &appsv1.Deployment{}
+ Expect(yaml.Unmarshal(doc, deployment)).To(Succeed())
+ if deployment.Name == deploymentName {
+ return deployment
+ }
+ }
+ }
+ }
+ Fail(fmt.Sprintf("Deployment %s not found in secret %s", deploymentName, matchedSecret.Name))
+ return nil // will never be reached, but makes the linter very happy
+}
+
+func deploymentExistsInSecret(ctx context.Context, fakeClient client.Client, namespace string, secretNamePrefix string, deploymentName string) bool {
+ GinkgoHelper()
+ secretList := &corev1.SecretList{}
+ Expect(fakeClient.List(ctx, secretList, client.InNamespace(namespace))).To(Succeed())
+ for _, s := range secretList.Items {
+ if !strings.HasPrefix(s.Name, secretNamePrefix) {
+ continue
+ }
+ for _, data := range s.Data {
+ for doc := range bytes.SplitSeq(data, []byte("\n---")) {
+ if bytes.Contains(doc, []byte("kind: Deployment")) {
+ deployment := &appsv1.Deployment{}
+ Expect(yaml.Unmarshal(doc, deployment)).To(Succeed())
+ if deployment.Name == deploymentName {
+ return true
+ }
+ }
+ }
+ }
+ }
+ return false
+}
+
+func findContainerInPod(podSpec *corev1.PodSpec, name string) (*corev1.Container, bool) {
+ for i := range podSpec.Containers {
+ if podSpec.Containers[i].Name == name {
+ return &podSpec.Containers[i], true
+ }
+ }
+ return nil, false
+}
diff --git a/pkg/controller/controlplane/valuesprovider.go b/pkg/controller/controlplane/valuesprovider.go
index 180ac234..9f97ee88 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, string, *stackitv1alpha1.ControlPlaneConfig, map[string]any) error
+ HandleShootCSICompatibility(context.Context, string, 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, cluster.Shoot.Spec.Kubernetes.Version, 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, cluster.Shoot.Spec.Kubernetes.Version, 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..336176cd 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, string, *stackitv1alpha1.ControlPlaneConfig, map[string]any) error {
+ return nil
+}
+func (*noopCSICompatibilityHandler) HandleShootCSICompatibility(context.Context, string, string, *stackitv1alpha1.ControlPlaneConfig, map[string]any) error {
+ return nil
+}
diff --git a/test/integration/controlplane/controlplane_suite_test.go b/test/integration/controlplane/controlplane_suite_test.go
new file mode 100644
index 00000000..bae167c5
--- /dev/null
+++ b/test/integration/controlplane/controlplane_suite_test.go
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package controlplane
+
+import (
+ "flag"
+ "testing"
+
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+)
+
+func init() {
+ // Accepted for compatibility with the shared make test-integration invocation.
+ flag.String("region", "eu01", "Region")
+}
+
+func TestControlPlane(t *testing.T) {
+ RegisterFailHandler(Fail)
+ RunSpecs(t, "ControlPlane Controller Suite")
+}
diff --git a/test/integration/controlplane/controlplane_test.go b/test/integration/controlplane/controlplane_test.go
new file mode 100644
index 00000000..1be7b533
--- /dev/null
+++ b/test/integration/controlplane/controlplane_test.go
@@ -0,0 +1,396 @@
+// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package controlplane
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "path/filepath"
+ "time"
+
+ gardenerv1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
+ extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
+ resourcesv1alpha1 "github.com/gardener/gardener/pkg/apis/resources/v1alpha1"
+ "github.com/gardener/gardener/pkg/client/kubernetes"
+ "github.com/gardener/gardener/pkg/logger"
+ gardenerenvtest "github.com/gardener/gardener/test/envtest"
+ . "github.com/onsi/ginkgo/v2"
+ . "github.com/onsi/gomega"
+ corev1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/labels"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/apimachinery/pkg/runtime/serializer"
+ runtimejson "k8s.io/apimachinery/pkg/runtime/serializer/json"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/uuid"
+ "sigs.k8s.io/controller-runtime/pkg/cache"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller"
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+ "sigs.k8s.io/controller-runtime/pkg/manager"
+ metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
+
+ stackitv1alpha1 "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/apis/stackit/v1alpha1"
+ "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/controller/controlplane"
+ "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/openstack"
+ "github.com/stackitcloud/gardener-extension-provider-stackit/v2/pkg/stackit"
+)
+
+var (
+ ctx = context.Background()
+
+ testEnv *gardenerenvtest.GardenerTestEnvironment
+ mgrCancel context.CancelFunc
+ k8sclient client.Client
+ encoder runtime.Encoder
+
+ testID = string(uuid.NewUUID())
+)
+
+var _ = BeforeSuite(func() {
+ logf.SetLogger(logger.MustNewZapLogger(logger.DebugLevel, logger.FormatJSON, zap.WriteTo(GinkgoWriter)))
+
+ repoRoot := filepath.Join("..", "..", "..")
+
+ DeferCleanup(func() {
+ By("stopping manager")
+ mgrCancel()
+
+ By("stopping test environment")
+ Expect(testEnv.Stop()).To(Succeed())
+ })
+
+ By("starting test environment")
+ testEnv = &gardenerenvtest.GardenerTestEnvironment{
+ Environment: &envtest.Environment{
+ CRDInstallOptions: envtest.CRDInstallOptions{
+ Paths: []string{
+ filepath.Join(repoRoot, "test", "integration", "testdata", "upstream-crds", "10-crd-extensions.gardener.cloud_clusters.yaml"),
+ filepath.Join(repoRoot, "test", "integration", "testdata", "upstream-crds", "10-crd-extensions.gardener.cloud_controlplanes.yaml"),
+ filepath.Join(repoRoot, "test", "integration", "testdata", "upstream-crds", "10-crd-resources.gardener.cloud_managedresources.yaml"),
+ filepath.Join(repoRoot, "test", "integration", "testdata", "upstream-crds", "10-crd-autoscaling.k8s.io_verticalpodautoscalers.yaml"),
+ },
+ },
+ },
+ GardenerAPIServer: &gardenerenvtest.GardenerAPIServer{},
+ }
+
+ restConfig, err := testEnv.Start()
+ Expect(err).ToNot(HaveOccurred())
+ Expect(restConfig).ToNot(BeNil())
+
+ scheme := runtime.NewScheme()
+ Expect(kubernetes.AddSeedSchemeToScheme(scheme)).To(Succeed())
+ Expect(stackitv1alpha1.AddToScheme(scheme)).To(Succeed())
+ Expect(gardenerv1beta1.AddToScheme(scheme)).To(Succeed())
+
+ By("setup manager")
+ mgr, err := manager.New(restConfig, manager.Options{
+ Scheme: scheme,
+ Metrics: metricsserver.Options{
+ BindAddress: "0",
+ },
+ Cache: cache.Options{
+ ByObject: map[client.Object]cache.ByObject{
+ &extensionsv1alpha1.ControlPlane{}: {
+ Label: labels.SelectorFromSet(labels.Set{"test-id": testID}),
+ },
+ },
+ },
+ })
+ Expect(err).NotTo(HaveOccurred())
+
+ Expect(controlplane.AddToManagerWithOptions(ctx, mgr, controlplane.AddOptions{
+ Controller: controller.Options{
+ MaxConcurrentReconciles: 1,
+ },
+ })).To(Succeed())
+
+ var mgrCtx context.Context
+ mgrCtx, mgrCancel = context.WithCancel(ctx)
+
+ By("start manager")
+ go func() {
+ defer GinkgoRecover()
+ Expect(mgr.Start(mgrCtx)).To(Succeed())
+ }()
+
+ k8sclient = mgr.GetClient()
+
+ gv := schema.GroupVersions{
+ stackitv1alpha1.SchemeGroupVersion,
+ gardenerv1beta1.SchemeGroupVersion,
+ }
+ encoder = serializer.NewCodecFactory(scheme).EncoderForVersion(&runtimejson.Serializer{}, gv)
+})
+
+var _ = Describe("ControlPlane CSI compatibility mode", func() {
+ var namespace string
+
+ BeforeEach(func() {
+ namespace = "shoot--test--" + testID[:8]
+
+ By("create namespace")
+ ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
+ Expect(k8sclient.Create(ctx, ns)).To(Succeed())
+ DeferCleanup(func() { Expect(client.IgnoreNotFound(k8sclient.Delete(ctx, ns))).To(Succeed()) })
+
+ By("create cluster")
+ shoot := &gardenerv1beta1.Shoot{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: "core.gardener.cloud/v1beta1",
+ Kind: "Shoot",
+ },
+ ObjectMeta: metav1.ObjectMeta{Name: "test"},
+ Spec: gardenerv1beta1.ShootSpec{
+ Kubernetes: gardenerv1beta1.Kubernetes{Version: "1.33.5"},
+ },
+ Status: gardenerv1beta1.ShootStatus{TechnicalID: namespace},
+ }
+ shootBytes := new(bytes.Buffer)
+ Expect(encoder.Encode(shoot, shootBytes)).To(Succeed())
+
+ cluster := &extensionsv1alpha1.Cluster{
+ ObjectMeta: metav1.ObjectMeta{Name: namespace},
+ Spec: extensionsv1alpha1.ClusterSpec{
+ CloudProfile: runtime.RawExtension{Raw: []byte(`{}`)},
+ Seed: runtime.RawExtension{Raw: []byte(`{}`)},
+ Shoot: runtime.RawExtension{Raw: shootBytes.Bytes()},
+ },
+ }
+ Expect(k8sclient.Create(ctx, cluster)).To(Succeed())
+ DeferCleanup(func() { Expect(client.IgnoreNotFound(k8sclient.Delete(ctx, cluster))).To(Succeed()) })
+
+ By("create cloudprovider secret")
+ cloudproviderSecret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{Name: "cloudprovider", Namespace: namespace},
+ Data: map[string][]byte{
+ stackit.ProjectID: []byte("test-project-id"),
+ stackit.SaKeyJSON: []byte(`{"key":"value"}`),
+ },
+ }
+ Expect(k8sclient.Create(ctx, cloudproviderSecret)).To(Succeed())
+ DeferCleanup(func() { Expect(client.IgnoreNotFound(k8sclient.Delete(ctx, cloudproviderSecret))).To(Succeed()) })
+
+ By("create cloud-provider-config secret")
+ cpConfigSecret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{Name: openstack.CloudProviderConfigName, Namespace: namespace},
+ Data: map[string][]byte{"config": []byte("placeholder")},
+ }
+ Expect(k8sclient.Create(ctx, cpConfigSecret)).To(Succeed())
+ DeferCleanup(func() { Expect(client.IgnoreNotFound(k8sclient.Delete(ctx, cpConfigSecret))).To(Succeed()) })
+
+ By("create cloud-provider-disk-config-csi secret")
+ diskConfigSecret := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{Name: openstack.CloudProviderCSIDiskConfigName, Namespace: namespace},
+ Data: map[string][]byte{"config": []byte("placeholder")},
+ }
+ Expect(k8sclient.Create(ctx, diskConfigSecret)).To(Succeed())
+ DeferCleanup(func() { Expect(client.IgnoreNotFound(k8sclient.Delete(ctx, diskConfigSecret))).To(Succeed()) })
+ })
+
+ It("should create both ManagedResources when compatibilityMode=compat", func() {
+ cpConfig := &stackitv1alpha1.ControlPlaneConfig{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: stackitv1alpha1.SchemeGroupVersion.String(),
+ Kind: "ControlPlaneConfig",
+ },
+ CloudControllerManager: &stackitv1alpha1.CloudControllerManagerConfig{
+ Name: string(stackitv1alpha1.STACKIT),
+ },
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ Name: string(stackitv1alpha1.STACKIT),
+ CompatibilityMode: string(stackitv1alpha1.COMPAT),
+ },
+ },
+ }
+ cpConfigBytes, err := json.Marshal(cpConfig)
+ Expect(err).NotTo(HaveOccurred())
+
+ infraStatus := &stackitv1alpha1.InfrastructureStatus{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: stackitv1alpha1.SchemeGroupVersion.String(),
+ Kind: "InfrastructureStatus",
+ },
+ Networks: stackitv1alpha1.NetworkStatus{
+ ID: "test-network-id",
+ Name: "test-network-name",
+ Router: stackitv1alpha1.RouterStatus{
+ ID: "test-router-id",
+ },
+ },
+ Node: stackitv1alpha1.NodeStatus{KeyName: "test-key"},
+ }
+ infraStatusBytes, err := json.Marshal(infraStatus)
+ Expect(err).NotTo(HaveOccurred())
+
+ By("create ControlPlane CR")
+ cp := &extensionsv1alpha1.ControlPlane{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "controlplane",
+ Namespace: namespace,
+ Labels: map[string]string{"test-id": testID},
+ },
+ Spec: extensionsv1alpha1.ControlPlaneSpec{
+ DefaultSpec: extensionsv1alpha1.DefaultSpec{
+ Type: stackit.Type,
+ ProviderConfig: &runtime.RawExtension{Raw: cpConfigBytes},
+ },
+ Region: "eu01",
+ SecretRef: corev1.SecretReference{
+ Name: "cloudprovider",
+ Namespace: namespace,
+ },
+ InfrastructureProviderStatus: &runtime.RawExtension{Raw: infraStatusBytes},
+ },
+ }
+ Expect(k8sclient.Create(ctx, cp)).To(Succeed())
+ DeferCleanup(func() { Expect(client.IgnoreNotFound(k8sclient.Delete(ctx, cp))).To(Succeed()) })
+
+ By("wait for seed ManagedResource")
+ seedMR := &resourcesv1alpha1.ManagedResource{}
+ Eventually(func() error {
+ return k8sclient.Get(ctx, types.NamespacedName{
+ Name: "stackit-compatibility-chart",
+ Namespace: namespace,
+ }, seedMR)
+ }).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())
+
+ By("wait for shoot ManagedResource")
+ shootMR := &resourcesv1alpha1.ManagedResource{}
+ Eventually(func() error {
+ return k8sclient.Get(ctx, types.NamespacedName{
+ Name: "stackit-compatibility-shoot-chart",
+ Namespace: namespace,
+ }, shootMR)
+ }).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())
+ })
+
+ It("should delete both ManagedResources when transitioning from compat to default", func() {
+ cpConfigCompat := &stackitv1alpha1.ControlPlaneConfig{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: stackitv1alpha1.SchemeGroupVersion.String(),
+ Kind: "ControlPlaneConfig",
+ },
+ CloudControllerManager: &stackitv1alpha1.CloudControllerManagerConfig{
+ Name: string(stackitv1alpha1.STACKIT),
+ },
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ Name: string(stackitv1alpha1.STACKIT),
+ CompatibilityMode: string(stackitv1alpha1.COMPAT),
+ },
+ },
+ }
+ cpConfigCompatBytes, err := json.Marshal(cpConfigCompat)
+ Expect(err).NotTo(HaveOccurred())
+
+ infraStatus := &stackitv1alpha1.InfrastructureStatus{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: stackitv1alpha1.SchemeGroupVersion.String(),
+ Kind: "InfrastructureStatus",
+ },
+ Networks: stackitv1alpha1.NetworkStatus{
+ ID: "test-network-id",
+ Name: "test-network-name",
+ Router: stackitv1alpha1.RouterStatus{
+ ID: "test-router-id",
+ },
+ },
+ Node: stackitv1alpha1.NodeStatus{KeyName: "test-key"},
+ }
+ infraStatusBytes, err := json.Marshal(infraStatus)
+ Expect(err).NotTo(HaveOccurred())
+
+ By("create ControlPlane CR with compat mode")
+ cp := &extensionsv1alpha1.ControlPlane{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "controlplane",
+ Namespace: namespace,
+ Labels: map[string]string{"test-id": testID},
+ },
+ Spec: extensionsv1alpha1.ControlPlaneSpec{
+ DefaultSpec: extensionsv1alpha1.DefaultSpec{
+ Type: stackit.Type,
+ ProviderConfig: &runtime.RawExtension{Raw: cpConfigCompatBytes},
+ },
+ Region: "eu01",
+ SecretRef: corev1.SecretReference{
+ Name: "cloudprovider",
+ Namespace: namespace,
+ },
+ InfrastructureProviderStatus: &runtime.RawExtension{Raw: infraStatusBytes},
+ },
+ }
+ Expect(k8sclient.Create(ctx, cp)).To(Succeed())
+ DeferCleanup(func() { Expect(client.IgnoreNotFound(k8sclient.Delete(ctx, cp))).To(Succeed()) })
+
+ By("wait for seed ManagedResource to be created")
+ Eventually(func() error {
+ return k8sclient.Get(ctx, types.NamespacedName{
+ Name: "stackit-compatibility-chart",
+ Namespace: namespace,
+ }, &resourcesv1alpha1.ManagedResource{})
+ }).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())
+
+ By("wait for shoot ManagedResource to be created")
+ Eventually(func() error {
+ return k8sclient.Get(ctx, types.NamespacedName{
+ Name: "stackit-compatibility-shoot-chart",
+ Namespace: namespace,
+ }, &resourcesv1alpha1.ManagedResource{})
+ }).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())
+
+ By("update ControlPlane to default compatibility mode")
+ cpConfigDefault := &stackitv1alpha1.ControlPlaneConfig{
+ TypeMeta: metav1.TypeMeta{
+ APIVersion: stackitv1alpha1.SchemeGroupVersion.String(),
+ Kind: "ControlPlaneConfig",
+ },
+ CloudControllerManager: &stackitv1alpha1.CloudControllerManagerConfig{
+ Name: string(stackitv1alpha1.STACKIT),
+ },
+ Storage: &stackitv1alpha1.Storage{
+ CSI: &stackitv1alpha1.CSI{
+ Name: string(stackitv1alpha1.STACKIT),
+ CompatibilityMode: string(stackitv1alpha1.DEFAULT),
+ },
+ },
+ }
+ cpConfigDefaultBytes, err := json.Marshal(cpConfigDefault)
+ Expect(err).NotTo(HaveOccurred())
+
+ By("trigger reconciliation")
+ Expect(k8sclient.Get(ctx, types.NamespacedName{Name: cp.Name, Namespace: cp.Namespace}, cp)).To(Succeed())
+ cp.Spec.ProviderConfig = &runtime.RawExtension{Raw: cpConfigDefaultBytes}
+ metav1.SetMetaDataAnnotation(&cp.ObjectMeta, "gardener.cloud/operation", "reconcile")
+ Expect(k8sclient.Update(ctx, cp)).To(Succeed())
+
+ By("wait for seed ManagedResource to be deleted")
+ Eventually(func() bool {
+ err := k8sclient.Get(ctx, types.NamespacedName{
+ Name: "stackit-compatibility-chart",
+ Namespace: namespace,
+ }, &resourcesv1alpha1.ManagedResource{})
+ return apierrors.IsNotFound(err)
+ }).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(BeTrue())
+
+ By("wait for shoot ManagedResource to be deleted")
+ Eventually(func() bool {
+ err := k8sclient.Get(ctx, types.NamespacedName{
+ Name: "stackit-compatibility-shoot-chart",
+ Namespace: namespace,
+ }, &resourcesv1alpha1.ManagedResource{})
+ return apierrors.IsNotFound(err)
+ }).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(BeTrue())
+ })
+})
diff --git a/test/integration/testdata/upstream-crds/10-crd-autoscaling.k8s.io_verticalpodautoscalers.yaml b/test/integration/testdata/upstream-crds/10-crd-autoscaling.k8s.io_verticalpodautoscalers.yaml
new file mode 100644
index 00000000..bf6f0b3d
--- /dev/null
+++ b/test/integration/testdata/upstream-crds/10-crd-autoscaling.k8s.io_verticalpodautoscalers.yaml
@@ -0,0 +1,630 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ api-approved.kubernetes.io: https://github.com/kubernetes/kubernetes/pull/63797
+ controller-gen.kubebuilder.io/version: v0.20.1
+ name: verticalpodautoscalers.autoscaling.k8s.io
+spec:
+ group: autoscaling.k8s.io
+ names:
+ kind: VerticalPodAutoscaler
+ listKind: VerticalPodAutoscalerList
+ plural: verticalpodautoscalers
+ shortNames:
+ - vpa
+ singular: verticalpodautoscaler
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - jsonPath: .spec.updatePolicy.updateMode
+ name: Mode
+ type: string
+ - jsonPath: .status.recommendation.containerRecommendations[0].target.cpu
+ name: CPU
+ type: string
+ - jsonPath: .status.recommendation.containerRecommendations[0].target.memory
+ name: Mem
+ type: string
+ - jsonPath: .status.conditions[?(@.type=='RecommendationProvided')].status
+ name: Provided
+ type: string
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1
+ schema:
+ openAPIV3Schema:
+ description: |-
+ VerticalPodAutoscaler is the configuration for a vertical pod
+ autoscaler, which automatically manages pod resources based on historical and
+ real time resource utilization.
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: |-
+ Specification of the behavior of the autoscaler.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.
+ properties:
+ recommenders:
+ description: |-
+ Recommender responsible for generating recommendation for this object.
+ List should be empty (then the default recommender will generate the
+ recommendation) or contain exactly one recommender.
+ items:
+ description: |-
+ VerticalPodAutoscalerRecommenderSelector points to a specific Vertical Pod Autoscaler recommender.
+ In the future it might pass parameters to the recommender.
+ properties:
+ name:
+ description: Name of the recommender responsible for generating
+ recommendation for this object.
+ type: string
+ required:
+ - name
+ type: object
+ type: array
+ resourcePolicy:
+ description: |-
+ Controls how the autoscaler computes recommended resources.
+ The resource policy may be used to set constraints on the recommendations
+ for individual containers.
+ If any individual containers need to be excluded from getting the VPA recommendations, then
+ it must be disabled explicitly by setting mode to "Off" under containerPolicies.
+ If not specified, the autoscaler computes recommended resources for all containers in the pod,
+ without additional constraints.
+ properties:
+ containerPolicies:
+ description: Per-container resource policies.
+ items:
+ description: |-
+ ContainerResourcePolicy controls how autoscaler computes the recommended
+ resources for a specific container.
+ properties:
+ containerName:
+ description: |-
+ Name of the container or DefaultContainerResourcePolicy, in which
+ case the policy is used by the containers that don't have their own
+ policy specified.
+ type: string
+ controlledResources:
+ description: |-
+ Specifies the type of recommendations that will be computed
+ (and possibly applied) by VPA.
+ If not specified, the default of [ResourceCPU, ResourceMemory] will be used.
+ items:
+ description: ResourceName is the name identifying various
+ resources in a ResourceList.
+ type: string
+ type: array
+ controlledValues:
+ description: |-
+ Specifies which resource values should be controlled.
+ The default is "RequestsAndLimits".
+ enum:
+ - RequestsAndLimits
+ - RequestsOnly
+ type: string
+ maxAllowed:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Specifies the maximum amount of resources that will be recommended
+ for the container. The default is no maximum.
+ type: object
+ minAllowed:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Specifies the minimal amount of resources that will be recommended
+ for the container. The default is no minimum.
+ type: object
+ mode:
+ description: Whether autoscaler is enabled for the container.
+ The default is "Auto".
+ enum:
+ - Auto
+ - "Off"
+ type: string
+ oomBumpUpRatio:
+ anyOf:
+ - type: integer
+ - type: string
+ description: oomBumpUpRatio is the ratio to increase memory
+ when OOM is detected.
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ oomMinBumpUp:
+ anyOf:
+ - type: integer
+ - type: string
+ description: oomMinBumpUp is the minimum increase in memory
+ when OOM is detected.
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ type: object
+ type: array
+ type: object
+ targetRef:
+ description: |-
+ TargetRef points to the controller managing the set of pods for the
+ autoscaler to control - e.g. Deployment, StatefulSet. VerticalPodAutoscaler
+ can be targeted at controller implementing scale subresource (the pod set is
+ retrieved from the controller's ScaleStatus) or some well known controllers
+ (e.g. for DaemonSet the pod set is read from the controller's spec).
+ If VerticalPodAutoscaler cannot use specified target it will report
+ ConfigUnsupported condition.
+ Note that VerticalPodAutoscaler does not require full implementation
+ of scale subresource - it will not use it to modify the replica count.
+ The only thing retrieved is a label selector matching pods grouped by
+ the target resource.
+ properties:
+ apiVersion:
+ description: apiVersion is the API version of the referent
+ type: string
+ kind:
+ description: 'kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ name:
+ description: 'name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ x-kubernetes-map-type: atomic
+ updatePolicy:
+ description: |-
+ Describes the rules on how changes are applied to the pods.
+ If not specified, all fields in the `PodUpdatePolicy` are set to their
+ default values.
+ properties:
+ evictionRequirements:
+ description: |-
+ EvictionRequirements is a list of EvictionRequirements that need to
+ evaluate to true in order for a Pod to be evicted. If more than one
+ EvictionRequirement is specified, all of them need to be fulfilled to allow eviction.
+ items:
+ description: |-
+ EvictionRequirement defines a single condition which needs to be true in
+ order to evict a Pod
+ properties:
+ changeRequirement:
+ description: EvictionChangeRequirement refers to the relationship
+ between the new target recommendation for a Pod and its
+ current requests, what kind of change is necessary for
+ the Pod to be evicted
+ enum:
+ - TargetHigherThanRequests
+ - TargetLowerThanRequests
+ type: string
+ resources:
+ description: |-
+ Resources is a list of one or more resources that the condition applies
+ to. If more than one resource is given, the EvictionRequirement is fulfilled
+ if at least one resource meets `changeRequirement`.
+ items:
+ description: ResourceName is the name identifying various
+ resources in a ResourceList.
+ type: string
+ type: array
+ required:
+ - changeRequirement
+ - resources
+ type: object
+ type: array
+ minReplicas:
+ description: |-
+ Minimal number of replicas which need to be alive for Updater to attempt
+ pod eviction (pending other checks like PDB). Only positive values are
+ allowed. Overrides global '--min-replicas' flag.
+ format: int32
+ type: integer
+ updateMode:
+ description: |-
+ Controls when autoscaler applies changes to the pod resources.
+ The default is 'Recreate'.
+ enum:
+ - "Off"
+ - Initial
+ - Recreate
+ - InPlaceOrRecreate
+ - Auto
+ type: string
+ type: object
+ required:
+ - targetRef
+ type: object
+ status:
+ description: Current information about the autoscaler.
+ properties:
+ conditions:
+ description: |-
+ Conditions is the set of conditions required for this autoscaler to scale its target,
+ and indicates whether or not those conditions are met.
+ items:
+ description: |-
+ VerticalPodAutoscalerCondition describes the state of
+ a VerticalPodAutoscaler at a certain point.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from
+ one status to another
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human-readable explanation containing details about
+ the transition
+ type: string
+ reason:
+ description: reason is the reason for the condition's last transition.
+ type: string
+ status:
+ description: status is the status of the condition (True, False,
+ Unknown)
+ type: string
+ type:
+ description: type describes the current condition
+ type: string
+ required:
+ - status
+ - type
+ type: object
+ type: array
+ recommendation:
+ description: |-
+ The most recently computed amount of resources recommended by the
+ autoscaler for the controlled pods.
+ properties:
+ containerRecommendations:
+ description: Resources recommended by the autoscaler for each
+ container.
+ items:
+ description: |-
+ RecommendedContainerResources is the recommendation of resources computed by
+ autoscaler for a specific container. Respects the container resource policy
+ if present in the spec. In particular the recommendation is not produced for
+ containers with `ContainerScalingMode` set to 'Off'.
+ properties:
+ containerName:
+ description: Name of the container.
+ type: string
+ lowerBound:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Minimum recommended amount of resources. Observes ContainerResourcePolicy.
+ This amount is not guaranteed to be sufficient for the application to operate in a stable way, however
+ running with less resources is likely to have significant impact on performance/availability.
+ type: object
+ target:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: Recommended amount of resources. Observes ContainerResourcePolicy.
+ type: object
+ uncappedTarget:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ The most recent recommended resources target computed by the autoscaler
+ for the controlled pods, based only on actual resource usage, not taking
+ into account the ContainerResourcePolicy.
+ May differ from the Recommendation if the actual resource usage causes
+ the target to violate the ContainerResourcePolicy (lower than MinAllowed
+ or higher that MaxAllowed).
+ Used only as status indication, will not affect actual resource assignment.
+ type: object
+ upperBound:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Maximum recommended amount of resources. Observes ContainerResourcePolicy.
+ Any resources allocated beyond this value are likely wasted. This value may be larger than the maximum
+ amount of application is actually capable of consuming.
+ type: object
+ required:
+ - target
+ type: object
+ type: array
+ type: object
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+ - deprecated: true
+ deprecationWarning: autoscaling.k8s.io/v1beta2 API is deprecated
+ name: v1beta2
+ schema:
+ openAPIV3Schema:
+ description: |-
+ VerticalPodAutoscaler is the configuration for a vertical pod
+ autoscaler, which automatically manages pod resources based on historical and
+ real time resource utilization.
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: |-
+ Specification of the behavior of the autoscaler.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status.
+ properties:
+ resourcePolicy:
+ description: |-
+ Controls how the autoscaler computes recommended resources.
+ The resource policy may be used to set constraints on the recommendations
+ for individual containers. If not specified, the autoscaler computes recommended
+ resources for all containers in the pod, without additional constraints.
+ properties:
+ containerPolicies:
+ description: Per-container resource policies.
+ items:
+ description: |-
+ ContainerResourcePolicy controls how autoscaler computes the recommended
+ resources for a specific container.
+ properties:
+ containerName:
+ description: |-
+ Name of the container or DefaultContainerResourcePolicy, in which
+ case the policy is used by the containers that don't have their own
+ policy specified.
+ type: string
+ maxAllowed:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Specifies the maximum amount of resources that will be recommended
+ for the container. The default is no maximum.
+ type: object
+ minAllowed:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Specifies the minimal amount of resources that will be recommended
+ for the container. The default is no minimum.
+ type: object
+ mode:
+ description: Whether autoscaler is enabled for the container.
+ The default is "Auto".
+ enum:
+ - Auto
+ - "Off"
+ type: string
+ type: object
+ type: array
+ type: object
+ targetRef:
+ description: |-
+ TargetRef points to the controller managing the set of pods for the
+ autoscaler to control - e.g. Deployment, StatefulSet. VerticalPodAutoscaler
+ can be targeted at controller implementing scale subresource (the pod set is
+ retrieved from the controller's ScaleStatus) or some well known controllers
+ (e.g. for DaemonSet the pod set is read from the controller's spec).
+ If VerticalPodAutoscaler cannot use specified target it will report
+ ConfigUnsupported condition.
+ Note that VerticalPodAutoscaler does not require full implementation
+ of scale subresource - it will not use it to modify the replica count.
+ The only thing retrieved is a label selector matching pods grouped by
+ the target resource.
+ properties:
+ apiVersion:
+ description: apiVersion is the API version of the referent
+ type: string
+ kind:
+ description: 'kind is the kind of the referent; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ name:
+ description: 'name is the name of the referent; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ x-kubernetes-map-type: atomic
+ updatePolicy:
+ description: |-
+ Describes the rules on how changes are applied to the pods.
+ If not specified, all fields in the `PodUpdatePolicy` are set to their
+ default values.
+ properties:
+ updateMode:
+ description: |-
+ Controls when autoscaler applies changes to the pod resources.
+ The default is 'Auto'.
+ enum:
+ - "Off"
+ - Initial
+ - Recreate
+ - Auto
+ type: string
+ type: object
+ required:
+ - targetRef
+ type: object
+ status:
+ description: Current information about the autoscaler.
+ properties:
+ conditions:
+ description: |-
+ Conditions is the set of conditions required for this autoscaler to scale its target,
+ and indicates whether or not those conditions are met.
+ items:
+ description: |-
+ VerticalPodAutoscalerCondition describes the state of
+ a VerticalPodAutoscaler at a certain point.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from
+ one status to another
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human-readable explanation containing details about
+ the transition
+ type: string
+ reason:
+ description: reason is the reason for the condition's last transition.
+ type: string
+ status:
+ description: status is the status of the condition (True, False,
+ Unknown)
+ type: string
+ type:
+ description: type describes the current condition
+ type: string
+ required:
+ - status
+ - type
+ type: object
+ type: array
+ recommendation:
+ description: |-
+ The most recently computed amount of resources recommended by the
+ autoscaler for the controlled pods.
+ properties:
+ containerRecommendations:
+ description: Resources recommended by the autoscaler for each
+ container.
+ items:
+ description: |-
+ RecommendedContainerResources is the recommendation of resources computed by
+ autoscaler for a specific container. Respects the container resource policy
+ if present in the spec. In particular the recommendation is not produced for
+ containers with `ContainerScalingMode` set to 'Off'.
+ properties:
+ containerName:
+ description: Name of the container.
+ type: string
+ lowerBound:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Minimum recommended amount of resources. Observes ContainerResourcePolicy.
+ This amount is not guaranteed to be sufficient for the application to operate in a stable way, however
+ running with less resources is likely to have significant impact on performance/availability.
+ type: object
+ target:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: Recommended amount of resources. Observes ContainerResourcePolicy.
+ type: object
+ uncappedTarget:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ The most recent recommended resources target computed by the autoscaler
+ for the controlled pods, based only on actual resource usage, not taking
+ into account the ContainerResourcePolicy.
+ May differ from the Recommendation if the actual resource usage causes
+ the target to violate the ContainerResourcePolicy (lower than MinAllowed
+ or higher that MaxAllowed).
+ Used only as status indication, will not affect actual resource assignment.
+ type: object
+ upperBound:
+ additionalProperties:
+ anyOf:
+ - type: integer
+ - type: string
+ pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+ x-kubernetes-int-or-string: true
+ description: |-
+ Maximum recommended amount of resources. Observes ContainerResourcePolicy.
+ Any resources allocated beyond this value are likely wasted. This value may be larger than the maximum
+ amount of application is actually capable of consuming.
+ type: object
+ required:
+ - target
+ type: object
+ type: array
+ type: object
+ type: object
+ required:
+ - spec
+ type: object
+ served: false
+ storage: false
+ subresources:
+ status: {}
diff --git a/test/integration/testdata/upstream-crds/10-crd-extensions.gardener.cloud_controlplanes.yaml b/test/integration/testdata/upstream-crds/10-crd-extensions.gardener.cloud_controlplanes.yaml
new file mode 100644
index 00000000..bbb7d68b
--- /dev/null
+++ b/test/integration/testdata/upstream-crds/10-crd-extensions.gardener.cloud_controlplanes.yaml
@@ -0,0 +1,260 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.20.1
+ name: controlplanes.extensions.gardener.cloud
+spec:
+ group: extensions.gardener.cloud
+ names:
+ kind: ControlPlane
+ listKind: ControlPlaneList
+ plural: controlplanes
+ shortNames:
+ - cp
+ singular: controlplane
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: The control plane type.
+ jsonPath: .spec.type
+ name: Type
+ type: string
+ - description: Status of control plane resource.
+ jsonPath: .status.lastOperation.state
+ name: Status
+ type: string
+ - description: creation timestamp
+ jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: ControlPlane is a specification for a ControlPlane resource.
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: |-
+ Specification of the ControlPlane.
+ If the object's deletion timestamp is set, this field is immutable.
+ properties:
+ class:
+ description: Class holds the extension class used to control the responsibility
+ for multiple provider extensions.
+ type: string
+ x-kubernetes-validations:
+ - message: Value is immutable
+ rule: self == oldSelf
+ infrastructureProviderStatus:
+ description: |-
+ InfrastructureProviderStatus contains the provider status that has
+ been generated by the controller responsible for the `Infrastructure` resource.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ providerConfig:
+ description: ProviderConfig is the provider specific configuration.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ region:
+ description: Region is the region of this control plane. This field
+ is immutable.
+ type: string
+ secretRef:
+ description: SecretRef is a reference to a secret that contains the
+ cloud provider specific credentials.
+ properties:
+ name:
+ description: name is unique within a namespace to reference a
+ secret resource.
+ type: string
+ namespace:
+ description: namespace defines the space within which the secret
+ name must be unique.
+ type: string
+ type: object
+ x-kubernetes-map-type: atomic
+ type:
+ description: Type contains the instance of the resource's kind.
+ type: string
+ required:
+ - region
+ - secretRef
+ - type
+ type: object
+ status:
+ description: ControlPlaneStatus is the status of a ControlPlane resource.
+ properties:
+ conditions:
+ description: Conditions represents the latest available observations
+ of a Seed's current state.
+ items:
+ description: Condition holds the information about the state of
+ a resource.
+ properties:
+ codes:
+ description: Well-defined error codes in case the condition
+ reports a problem.
+ items:
+ description: ErrorCode is a string alias.
+ type: string
+ type: array
+ lastTransitionTime:
+ description: Last time the condition transitioned from one status
+ to another.
+ format: date-time
+ type: string
+ lastUpdateTime:
+ description: Last time the condition was updated.
+ format: date-time
+ type: string
+ message:
+ description: A human readable message indicating details about
+ the transition.
+ type: string
+ reason:
+ description: The reason for the condition's last transition.
+ type: string
+ status:
+ description: Status of the condition, one of True, False, Unknown.
+ type: string
+ type:
+ description: Type of the condition.
+ type: string
+ required:
+ - lastTransitionTime
+ - lastUpdateTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ lastError:
+ description: LastError holds information about the last occurred error
+ during an operation.
+ properties:
+ codes:
+ description: Well-defined error codes of the last error(s).
+ items:
+ description: ErrorCode is a string alias.
+ type: string
+ type: array
+ description:
+ description: A human readable message indicating details about
+ the last error.
+ type: string
+ lastUpdateTime:
+ description: Last time the error was reported
+ format: date-time
+ type: string
+ taskID:
+ description: ID of the task which caused this last error
+ type: string
+ required:
+ - description
+ type: object
+ lastOperation:
+ description: LastOperation holds information about the last operation
+ on the resource.
+ properties:
+ description:
+ description: A human readable message indicating details about
+ the last operation.
+ type: string
+ lastUpdateTime:
+ description: Last time the operation state transitioned from one
+ to another.
+ format: date-time
+ type: string
+ progress:
+ description: The progress in percentage (0-100) of the last operation.
+ format: int32
+ type: integer
+ state:
+ description: Status of the last operation, one of Aborted, Processing,
+ Succeeded, Error, Failed.
+ type: string
+ type:
+ description: Type of the last operation, one of Create, Reconcile,
+ Delete, Migrate, Restore.
+ type: string
+ required:
+ - description
+ - lastUpdateTime
+ - progress
+ - state
+ - type
+ type: object
+ observedGeneration:
+ description: ObservedGeneration is the most recent generation observed
+ for this resource.
+ format: int64
+ type: integer
+ providerStatus:
+ description: ProviderStatus contains provider-specific status.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ resources:
+ description: Resources holds a list of named resource references that
+ can be referred to in the state by their names.
+ items:
+ description: NamedResourceReference is a named reference to a resource.
+ properties:
+ name:
+ description: Name of the resource reference.
+ type: string
+ resourceRef:
+ description: ResourceRef is a reference to a resource.
+ properties:
+ apiVersion:
+ description: apiVersion is the API version of the referent
+ type: string
+ kind:
+ description: 'kind is the kind of the referent; More info:
+ https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ name:
+ description: 'name is the name of the referent; More info:
+ https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ x-kubernetes-map-type: atomic
+ required:
+ - name
+ - resourceRef
+ type: object
+ type: array
+ state:
+ description: State can be filled by the operating controller with
+ what ever data it needs.
+ type: object
+ x-kubernetes-preserve-unknown-fields: true
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
diff --git a/test/integration/testdata/upstream-crds/10-crd-resources.gardener.cloud_managedresources.yaml b/test/integration/testdata/upstream-crds/10-crd-resources.gardener.cloud_managedresources.yaml
new file mode 100644
index 00000000..dfdb95b7
--- /dev/null
+++ b/test/integration/testdata/upstream-crds/10-crd-resources.gardener.cloud_managedresources.yaml
@@ -0,0 +1,254 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.20.1
+ name: managedresources.resources.gardener.cloud
+spec:
+ group: resources.gardener.cloud
+ names:
+ kind: ManagedResource
+ listKind: ManagedResourceList
+ plural: managedresources
+ shortNames:
+ - mr
+ singular: managedresource
+ scope: Namespaced
+ versions:
+ - additionalPrinterColumns:
+ - description: The class identifies which resource manager is responsible for
+ this ManagedResource.
+ jsonPath: .spec.class
+ name: Class
+ type: string
+ - description: ' Indicates whether all resources have been applied.'
+ jsonPath: .status.conditions[?(@.type=="ResourcesApplied")].status
+ name: Applied
+ type: string
+ - description: Indicates whether all resources are healthy.
+ jsonPath: .status.conditions[?(@.type=="ResourcesHealthy")].status
+ name: Healthy
+ type: string
+ - description: Indicates whether some resources are still progressing to be rolled
+ out.
+ jsonPath: .status.conditions[?(@.type=="ResourcesProgressing")].status
+ name: Progressing
+ type: string
+ - description: creation timestamp
+ jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ description: ManagedResource describes a list of managed resources.
+ properties:
+ apiVersion:
+ description: |-
+ APIVersion defines the versioned schema of this representation of an object.
+ Servers should convert recognized schemas to the latest internal value, and
+ may reject unrecognized values.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+ type: string
+ kind:
+ description: |-
+ Kind is a string value representing the REST resource this object represents.
+ Servers may infer this from the endpoint the client submits requests to.
+ Cannot be updated.
+ In CamelCase.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Spec contains the specification of this managed resource.
+ properties:
+ class:
+ description: Class holds the resource class used to control the responsibility
+ for multiple resource manager instances
+ type: string
+ deletePersistentVolumeClaims:
+ description: |-
+ DeletePersistentVolumeClaims specifies if PersistentVolumeClaims created by StatefulSets, which are managed by this
+ resource, should also be deleted when the corresponding StatefulSet is deleted (defaults to false).
+ type: boolean
+ equivalences:
+ description: Equivalences specifies possible group/kind equivalences
+ for objects.
+ items:
+ items:
+ description: |-
+ GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying
+ concepts during lookup stages without having partially valid types
+ properties:
+ group:
+ type: string
+ kind:
+ type: string
+ required:
+ - group
+ - kind
+ type: object
+ type: array
+ type: array
+ forceOverwriteAnnotations:
+ description: ForceOverwriteAnnotations specifies that all existing
+ annotations should be overwritten. Defaults to false.
+ type: boolean
+ forceOverwriteLabels:
+ description: ForceOverwriteLabels specifies that all existing labels
+ should be overwritten. Defaults to false.
+ type: boolean
+ injectLabels:
+ additionalProperties:
+ type: string
+ description: InjectLabels injects the provided labels into every resource
+ that is part of the referenced secrets.
+ type: object
+ keepObjects:
+ description: |-
+ KeepObjects specifies whether the objects should be kept although the managed resource has already been deleted.
+ Defaults to false.
+ type: boolean
+ secretRefs:
+ description: SecretRefs is a list of secret references.
+ items:
+ description: |-
+ LocalObjectReference contains enough information to let you locate the
+ referenced object inside the same namespace.
+ properties:
+ name:
+ default: ""
+ description: |-
+ Name of the referent.
+ This field is effectively required, but due to backwards compatibility is
+ allowed to be empty. Instances of this type with an empty value here are
+ almost certainly wrong.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ type: string
+ type: object
+ x-kubernetes-map-type: atomic
+ type: array
+ required:
+ - secretRefs
+ type: object
+ status:
+ description: Status contains the status of this managed resource.
+ properties:
+ conditions:
+ items:
+ description: Condition holds the information about the state of
+ a resource.
+ properties:
+ codes:
+ description: Well-defined error codes in case the condition
+ reports a problem.
+ items:
+ description: ErrorCode is a string alias.
+ type: string
+ type: array
+ lastTransitionTime:
+ description: Last time the condition transitioned from one status
+ to another.
+ format: date-time
+ type: string
+ lastUpdateTime:
+ description: Last time the condition was updated.
+ format: date-time
+ type: string
+ message:
+ description: A human readable message indicating details about
+ the transition.
+ type: string
+ reason:
+ description: The reason for the condition's last transition.
+ type: string
+ status:
+ description: Status of the condition, one of True, False, Unknown.
+ type: string
+ type:
+ description: Type of the condition.
+ type: string
+ required:
+ - lastTransitionTime
+ - lastUpdateTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ observedGeneration:
+ description: ObservedGeneration is the most recent generation observed
+ for this resource.
+ format: int64
+ type: integer
+ resources:
+ description: Resources is a list of objects that have been created.
+ items:
+ description: ObjectReference is a reference to another object.
+ properties:
+ annotations:
+ additionalProperties:
+ type: string
+ description: Annotations is a map of annotations that were used
+ during last update of the resource.
+ type: object
+ apiVersion:
+ description: API version of the referent.
+ type: string
+ fieldPath:
+ description: |-
+ If referring to a piece of an object instead of an entire object, this string
+ should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
+ For example, if the object reference is to a container within a pod, this would take on a value like:
+ "spec.containers{name}" (where "name" refers to the name of the container that triggered
+ the event) or if no container name is specified "spec.containers[2]" (container with
+ index 2 in this pod). This syntax is chosen only to have some well-defined way of
+ referencing a part of an object.
+ type: string
+ kind:
+ description: |-
+ Kind of the referent.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+ type: string
+ labels:
+ additionalProperties:
+ type: string
+ description: Labels is a map of labels that were used during
+ last update of the resource.
+ type: object
+ name:
+ description: |-
+ Name of the referent.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+ type: string
+ namespace:
+ description: |-
+ Namespace of the referent.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
+ type: string
+ resourceVersion:
+ description: |-
+ Specific resourceVersion to which this reference is made, if any.
+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
+ type: string
+ uid:
+ description: |-
+ UID of the referent.
+ More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
+ type: string
+ type: object
+ x-kubernetes-map-type: atomic
+ type: array
+ secretsDataChecksum:
+ description: SecretsDataChecksum is the checksum of referenced secrets
+ data.
+ type: string
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}