diff --git a/crds/doc-ru-localstorageclass.yaml b/crds/doc-ru-localstorageclass.yaml index 4eae6565..37a1ff24 100644 --- a/crds/doc-ru-localstorageclass.yaml +++ b/crds/doc-ru-localstorageclass.yaml @@ -10,11 +10,6 @@ spec: description: | Описывает конфигурацию Kubernetes Storage Class. properties: - isDefault: - description: | - Следует ли использовать данный Storage class по умолчанию. - - > Обратите внимание, что false является значением по умолчанию. reclaimPolicy: description: | Reclaim policy данного storage class'а. Может быть: @@ -58,4 +53,4 @@ spec: - Create (если все операции завершились успешно) reason: description: | - Дополнительная информация о состоянии Storage Class. \ No newline at end of file + Дополнительная информация о состоянии Storage Class. diff --git a/crds/localstorageclass.yaml b/crds/localstorageclass.yaml index e52f3286..e15809cf 100644 --- a/crds/localstorageclass.yaml +++ b/crds/localstorageclass.yaml @@ -29,29 +29,13 @@ spec: properties: spec: type: object - x-kubernetes-validations: - - rule: "!has(self.fileSystem)" - message: "The 'fileSystem' field is not supported yet and cannot be used." - - rule: has(self.lvm) == has(oldSelf.lvm) && has(self.fileSystem) == has(oldSelf.fileSystem) - message: "Modification error: Once defined, 'lvm' or 'fileSystem' configuration cannot be replaced or removed. Ensure the existing storage configuration type is maintained." description: | Defines a Kubernetes Storage class configuration. required: - reclaimPolicy - volumeBindingMode - oneOf: - - required: - - lvm - - required: - - fileSystem + - lvm properties: - isDefault: - type: boolean - default: false - description: | - Should this Storage class be used as default. - - > Note that the default value is false. reclaimPolicy: type: string x-kubernetes-validations: @@ -120,16 +104,6 @@ spec: The name of the thin pool. minLength: 1 pattern: ^.*$ - fileSystem: - type: object - x-kubernetes-validations: - - rule: self == oldSelf - message: Value is immutable. - required: - - localPath - properties: - localPath: - type: string status: type: object description: | @@ -152,10 +126,11 @@ spec: - jsonPath: .status.phase name: Phase type: string - - jsonPath: .spec.isDefault - name: isDefault - type: boolean - jsonPath: .status.reason name: Reason type: string priority: 1 + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + description: The age of this resource diff --git a/docs/FAQ.md b/docs/FAQ.md index 03a96e9a..886550ee 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -16,9 +16,14 @@ In case of no free space in the pool, degradation in the module's operation as a ## How do I set the default StorageClass? -Set the `spec.IsDefault` field to `true` in the corresponding [LocalStorageClass](./cr.html#localstorageclass) custom resource. +Add the annotation `storageclass.kubernetes.io/is-default-class: "true"` to the corresponding StorageClass resource: + +```shell +kubectl annotate storageclasses.storage.k8s.io storageclass.kubernetes.io/is-default-class=true +``` ## I don't want the module to be used on all nodes of the cluster. How can I select the desired nodes? + The nodes that will be involved with the module are determined by special labels specified in the `nodeSelector` field in the module settings. To display and edit the module settings, you can execute the command: @@ -251,4 +256,4 @@ If the labels are present, it's necessary to check for the existence of the labe ```shell kubectl -n d8-sds-local-volume get po -l app=sds-local-volume-controller kubectl -n d8-sds-local-volume logs -l app=sds-local-volume-controller -``` \ No newline at end of file +``` diff --git a/docs/FAQ.ru.md b/docs/FAQ.ru.md index b4b62275..400aa175 100644 --- a/docs/FAQ.ru.md +++ b/docs/FAQ.ru.md @@ -16,7 +16,11 @@ Overprovisioning в LVMThin нужно использовать с осторо ## Как назначить StorageClass по умолчанию? -В соответствующем пользовательском ресурсе [LocalStorageClass](./cr.html#localstorageclass) в поле `spec.isDefault` указать `true`. +Добавьте аннотацию `storageclass.kubernetes.io/is-default-class: "true"` в соответствующий ресурс StorageClass: + +```shell +kubectl annotate storageclasses.storage.k8s.io storageclass.kubernetes.io/is-default-class=true +``` ## Я не хочу, чтобы модуль использовался на всех узлах кластера. Как мне выбрать желаемые узлы? Узлы, которые будут задействованы модулем, определяются специальными метками, указанными в поле `nodeSelector` в настройках модуля. @@ -247,4 +251,4 @@ kubectl label node %node-name% my-custom-label-key=my-custom-label-value ```shell kubectl -n d8-sds-local-volume get po -l app=sds-local-volume-controller kubectl -n d8-sds-local-volume logs -l app=sds-local-volume-controller -``` \ No newline at end of file +``` diff --git a/docs/README.md b/docs/README.md index 8edd57af..8e9a8dcf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -190,7 +190,6 @@ kind: LocalStorageClass metadata: name: local-storage-class spec: - isDefault: false lvm: lvmVolumeGroups: - name: vg-1-on-worker-0 diff --git a/images/sds-local-volume-controller/Dockerfile b/images/sds-local-volume-controller/Dockerfile index 62d7027d..b306a05b 100644 --- a/images/sds-local-volume-controller/Dockerfile +++ b/images/sds-local-volume-controller/Dockerfile @@ -1,7 +1,7 @@ ARG BASE_ALPINE=registry.deckhouse.io/base_images/alpine:3.16.3@sha256:5548e9172c24a1b0ca9afdd2bf534e265c94b12b36b3e0c0302f5853eaf00abb -ARG BASE_GOLANG_20_ALPINE_BUILDER=registry.deckhouse.io/base_images/golang:1.20.5-alpine3.18@sha256:51a47fb0851397db2f506c15c426735bc23de31177cbdd962880c0879d1906a4 +ARG BASE_GOLANG_22_ALPINE_BUILDER=registry.deckhouse.io/base_images/golang:1.22.1-alpine@sha256:0de6cf7cceab6ecbf0718bdfb675b08b78113c3709c5e4b99456cdb2ae8c2495 -FROM $BASE_GOLANG_20_ALPINE_BUILDER as builder +FROM $BASE_GOLANG_22_ALPINE_BUILDER as builder WORKDIR /go/src ADD go.mod . diff --git a/images/sds-local-volume-controller/api/v1alpha1/local_storage_class.go b/images/sds-local-volume-controller/api/v1alpha1/local_storage_class.go index 18bbee34..015ffe46 100644 --- a/images/sds-local-volume-controller/api/v1alpha1/local_storage_class.go +++ b/images/sds-local-volume-controller/api/v1alpha1/local_storage_class.go @@ -33,7 +33,6 @@ type LocalStorageClassList struct { } type LocalStorageClassSpec struct { - IsDefault bool `json:"isDefault"` ReclaimPolicy string `json:"reclaimPolicy"` VolumeBindingMode string `json:"volumeBindingMode"` LVM *LocalStorageClassLVM `json:"lvm,omitempty"` diff --git a/images/sds-local-volume-controller/cmd/main.go b/images/sds-local-volume-controller/cmd/main.go index 9cfad8a4..daf48148 100644 --- a/images/sds-local-volume-controller/cmd/main.go +++ b/images/sds-local-volume-controller/cmd/main.go @@ -26,7 +26,7 @@ import ( "sds-local-volume-controller/pkg/controller" "sds-local-volume-controller/pkg/kubutils" "sds-local-volume-controller/pkg/logger" - "sds-local-volume-controller/pkg/monitoring" + "sigs.k8s.io/controller-runtime/pkg/cache" v1 "k8s.io/api/core/v1" @@ -89,9 +89,8 @@ func main() { } managerOpts := manager.Options{ - Scheme: scheme, - Cache: cacheOpt, - //MetricsBindAddress: cfgParams.MetricsPort, + Scheme: scheme, + Cache: cacheOpt, LeaderElection: true, LeaderElectionNamespace: cfgParams.ControllerNamespace, LeaderElectionID: config.ControllerName, @@ -105,14 +104,12 @@ func main() { } log.Info("[main] successfully created kubernetes manager") - metrics := monitoring.GetMetrics("") - - if _, err = controller.RunLocalStorageClassWatcherController(mgr, *cfgParams, *log, metrics); err != nil { + if _, err = controller.RunLocalStorageClassWatcherController(mgr, *cfgParams, *log); err != nil { log.Error(err, fmt.Sprintf("[main] unable to run %s", controller.LocalStorageClassCtrlName)) os.Exit(1) } - if _, err = controller.RunLocalCSINodeWatcherController(mgr, *cfgParams, *log, metrics); err != nil { + if _, err = controller.RunLocalCSINodeWatcherController(mgr, *cfgParams, *log); err != nil { log.Error(err, fmt.Sprintf("[main] unable to run %s", controller.LocalCSINodeWatcherCtrl)) os.Exit(1) } diff --git a/images/sds-local-volume-controller/go.mod b/images/sds-local-volume-controller/go.mod index e4187805..36390bfc 100644 --- a/images/sds-local-volume-controller/go.mod +++ b/images/sds-local-volume-controller/go.mod @@ -1,18 +1,19 @@ module sds-local-volume-controller -go 1.21 +go 1.22 require ( - github.com/go-logr/logr v1.4.1 - github.com/prometheus/client_golang v1.18.0 - github.com/stretchr/testify v1.8.4 - k8s.io/api v0.29.1 - k8s.io/apiextensions-apiserver v0.29.1 - k8s.io/apimachinery v0.29.1 - k8s.io/client-go v0.29.1 + github.com/go-logr/logr v1.4.2 + github.com/onsi/ginkgo/v2 v2.19.0 + github.com/onsi/gomega v1.33.1 + github.com/stretchr/testify v1.9.0 + k8s.io/api v0.29.5 + k8s.io/apiextensions-apiserver v0.29.5 + k8s.io/apimachinery v0.29.5 + k8s.io/client-go v0.29.5 k8s.io/klog/v2 v2.120.1 - k8s.io/utils v0.0.0-20240102154912-e7106e64919e - sigs.k8s.io/controller-runtime v0.17.1 + k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 + sigs.k8s.io/controller-runtime v0.17.5 sigs.k8s.io/yaml v1.4.0 ) @@ -22,17 +23,19 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.8.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -44,25 +47,27 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.21.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.29.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/component-base v0.29.5 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect ) diff --git a/images/sds-local-volume-controller/go.sum b/images/sds-local-volume-controller/go.sum index 70762fd4..03584c3c 100644 --- a/images/sds-local-volume-controller/go.sum +++ b/images/sds-local-volume-controller/go.sum @@ -10,12 +10,12 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= -github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -24,27 +24,25 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= @@ -73,10 +71,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= -github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -100,8 +98,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -122,8 +120,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -132,23 +130,23 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -157,10 +155,8 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -172,24 +168,24 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= -k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= -k8s.io/apiextensions-apiserver v0.29.1 h1:S9xOtyk9M3Sk1tIpQMu9wXHm5O2MX6Y1kIpPMimZBZw= -k8s.io/apiextensions-apiserver v0.29.1/go.mod h1:zZECpujY5yTW58co8V2EQR4BD6A9pktVgHhvc0uLfeU= -k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= -k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= -k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= -k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= -k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= +k8s.io/api v0.29.5 h1:levS+umUigHCfI3riD36pMY1vQEbrzh4r1ivVWAhHaI= +k8s.io/api v0.29.5/go.mod h1:7b18TtPcJzdjk7w5zWyIHgoAtpGeRvGGASxlS7UZXdQ= +k8s.io/apiextensions-apiserver v0.29.5 h1:njDywexhE6n+1NEl3A4axT0TMQHREnndrk3/ztdWcNE= +k8s.io/apiextensions-apiserver v0.29.5/go.mod h1:pfIvij+MH9a8NQKtW7MD4EFnzvUjJ1ZQsDL8wuP8fnc= +k8s.io/apimachinery v0.29.5 h1:Hofa2BmPfpoT+IyDTlcPdCHSnHtEQMoJYGVoQpRTfv4= +k8s.io/apimachinery v0.29.5/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= +k8s.io/client-go v0.29.5 h1:nlASXmPQy190qTteaVP31g3c/wi2kycznkTP7Sv1zPc= +k8s.io/client-go v0.29.5/go.mod h1:aY5CnqUUvXYccJhm47XHoPcRyX6vouHdIBHaKZGTbK4= +k8s.io/component-base v0.29.5 h1:Ptj8AzG+p8c2a839XriHwxakDpZH9uvIgYz+o1agjg8= +k8s.io/component-base v0.29.5/go.mod h1:9nBUoPxW/yimISIgAG7sJDrUGJlu7t8HnDafIrOdU8Q= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.17.1 h1:V1dQELMGVk46YVXXQUbTFujU7u4DQj6YUj9Rb6cuzz8= -sigs.k8s.io/controller-runtime v0.17.1/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= +k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.17.5 h1:1FI9Lm7NiOOmBsgTV36/s2XrEFXnO2C4sbg/Zme72Rw= +sigs.k8s.io/controller-runtime v0.17.5/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/images/sds-local-volume-controller/pkg/controller/controller_suite_test.go b/images/sds-local-volume-controller/pkg/controller/controller_suite_test.go new file mode 100644 index 00000000..816c5825 --- /dev/null +++ b/images/sds-local-volume-controller/pkg/controller/controller_suite_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2023 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller_test + +import ( + "fmt" + "os" + v1alpha1 "sds-local-volume-controller/api/v1alpha1" + "testing" + + v1 "k8s.io/api/apps/v1" + + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + sv1 "k8s.io/api/storage/v1" + extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + + apiruntime "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestController(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Controller Suite") +} + +func NewFakeClient() client.Client { + resourcesSchemeFuncs := []func(*apiruntime.Scheme) error{ + v1alpha1.AddToScheme, + clientgoscheme.AddToScheme, + extv1.AddToScheme, + v1.AddToScheme, + sv1.AddToScheme, + } + scheme := apiruntime.NewScheme() + for _, f := range resourcesSchemeFuncs { + err := f(scheme) + if err != nil { + println(fmt.Sprintf("Error adding scheme: %s", err)) + os.Exit(1) + } + } + + // See https://github.com/kubernetes-sigs/controller-runtime/issues/2362#issuecomment-1837270195 + // builder := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&v1alpha1.NFSStorageClass{}) + builder := fake.NewClientBuilder().WithScheme(scheme) + cl := builder.Build() + return cl +} diff --git a/images/sds-local-volume-controller/pkg/controller/local_csi_node_watcher.go b/images/sds-local-volume-controller/pkg/controller/local_csi_node_watcher.go index 2655e077..2a8d3c64 100644 --- a/images/sds-local-volume-controller/pkg/controller/local_csi_node_watcher.go +++ b/images/sds-local-volume-controller/pkg/controller/local_csi_node_watcher.go @@ -19,12 +19,14 @@ package controller import ( "context" "fmt" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" "sds-local-volume-controller/api/v1alpha1" "sds-local-volume-controller/pkg/config" "sds-local-volume-controller/pkg/logger" - "sds-local-volume-controller/pkg/monitoring" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -32,8 +34,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" "sigs.k8s.io/yaml" - "strings" - "time" ) const ( @@ -48,7 +48,6 @@ func RunLocalCSINodeWatcherController( mgr manager.Manager, cfg config.Options, log logger.Logger, - metrics monitoring.Metrics, ) (controller.Controller, error) { cl := mgr.GetClient() diff --git a/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher.go b/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher.go index a827a6c4..4f1194e1 100644 --- a/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher.go +++ b/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher.go @@ -24,24 +24,18 @@ import ( v1alpha1 "sds-local-volume-controller/api/v1alpha1" "sds-local-volume-controller/pkg/config" "sds-local-volume-controller/pkg/logger" - "sds-local-volume-controller/pkg/monitoring" - "strings" "time" - corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/storage/v1" errors2 "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" - "k8s.io/utils/strings/slices" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "sigs.k8s.io/yaml" "sigs.k8s.io/controller-runtime/pkg/manager" ) @@ -49,10 +43,10 @@ import ( const ( LocalStorageClassCtrlName = "local-storage-class-controller" - Thin = "Thin" - Thick = "Thick" + LVMThinType = "Thin" + LVMThickType = "Thick" - Lvm = "lvm" + LocalStorageClassLvmType = "lvm" StorageClassKind = "StorageClass" StorageClassAPIVersion = "storage.k8s.io/v1" @@ -63,8 +57,8 @@ const ( LVMVolumeBindingModeParamKey = LocalStorageClassProvisioner + "/volume-binding-mode" LVMVolumeGroupsParamKey = LocalStorageClassProvisioner + "/lvm-volume-groups" - DefaultStorageClassAnnotationKey = "storageclass.kubernetes.io/is-default-class" - LocalStorageClassFinalizerName = "localstorageclass.storage.deckhouse.io" + LocalStorageClassFinalizerName = "storage.deckhouse.io/local-storage-class-controller" + LocalStorageClassFinalizerNameOld = "localstorageclass.storage.deckhouse.io" AllowVolumeExpansionDefaultValue = true @@ -84,13 +78,12 @@ func RunLocalStorageClassWatcherController( mgr manager.Manager, cfg config.Options, log logger.Logger, - metrics monitoring.Metrics, ) (controller.Controller, error) { cl := mgr.GetClient() c, err := controller.New(LocalStorageClassCtrlName, mgr, controller.Options{ Reconciler: reconcile.Func(func(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { - log.Info("[LocalStorageClassReconciler] starts Reconcile") + log.Info("[LocalStorageClassReconciler] starts Reconcile for the LocalStorageClass %q", request.Name) lsc := &v1alpha1.LocalStorageClass{} err := cl.Get(ctx, request.NamespacedName, lsc) if err != nil && !errors2.IsNotFound(err) { @@ -110,7 +103,7 @@ func RunLocalStorageClassWatcherController( return reconcile.Result{}, err } - shouldRequeue, err := runEventReconcile(ctx, cl, log, scList, lsc) + shouldRequeue, err := RunEventReconcile(ctx, cl, log, scList, lsc) if err != nil { log.Error(err, fmt.Sprintf("[LocalStorageClassReconciler] an error occured while reconciles the LocalStorageClass, name: %s", lsc.Name)) } @@ -122,7 +115,7 @@ func RunLocalStorageClassWatcherController( }, nil } - log.Info("[LocalStorageClassReconciler] ends Reconcile") + log.Info("[LocalStorageClassReconciler] ends Reconcile for the LocalStorageClass %q", request.Name) return reconcile.Result{}, nil }), }) @@ -133,47 +126,12 @@ func RunLocalStorageClassWatcherController( err = c.Watch(source.Kind(mgr.GetCache(), &v1alpha1.LocalStorageClass{}), handler.Funcs{ CreateFunc: func(ctx context.Context, e event.CreateEvent, q workqueue.RateLimitingInterface) { - log.Info(fmt.Sprintf("[CreateFunc] starts the reconciliation for the LocalStorageClass %s", e.Object.GetName())) - - lsc, ok := e.Object.(*v1alpha1.LocalStorageClass) - if !ok { - err = errors.New("unable to cast event object to a given type") - log.Error(err, "[CreateFunc] an error occurred while handling create event") - return - } - - scList := &v1.StorageClassList{} - err = cl.List(ctx, scList) - if err != nil { - log.Error(err, "[CreateFunc] unable to list Storage Classes") - err = updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, fmt.Sprintf("Unable to list storage classes, err: %s", err.Error())) - q.AddAfter(reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: lsc.Namespace, - Name: lsc.Name, - }, - }, cfg.RequeueStorageClassInterval*time.Second) - return - } - - shouldRequeue, err := runEventReconcile(ctx, cl, log, scList, lsc) - if err != nil { - log.Error(err, fmt.Sprintf("[CreateFunc] an error occured while reconciles the LocalStorageClass, name: %s", lsc.Name)) - } - - if shouldRequeue { - log.Warning(fmt.Sprintf("[CreateFunc] the LocalStorageClass %s event will be requeued", lsc.Name)) - q.AddAfter(reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: lsc.Namespace, - Name: lsc.Name, - }, - }, cfg.RequeueStorageClassInterval*time.Second) - } - log.Info(fmt.Sprintf("[CreateFunc] ends the reconciliation for the LocalStorageClass %s", e.Object.GetName())) + log.Info(fmt.Sprintf("[CreateFunc] get event for LocalStorageClass %q. Add to the queue", e.Object.GetName())) + request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: e.Object.GetNamespace(), Name: e.Object.GetName()}} + q.Add(request) }, UpdateFunc: func(ctx context.Context, e event.UpdateEvent, q workqueue.RateLimitingInterface) { - log.Info(fmt.Sprintf("[UpdateFunc] starts the reconciliation for the LocalStorageClass %s", e.ObjectNew.GetName())) + log.Info(fmt.Sprintf("[UpdateFunc] get event for LocalStorageClass %q. Check if it should be reconciled", e.ObjectNew.GetName())) oldLsc, ok := e.ObjectOld.(*v1alpha1.LocalStorageClass) if !ok { @@ -193,36 +151,9 @@ func RunLocalStorageClassWatcherController( return } - scList := &v1.StorageClassList{} - err = cl.List(ctx, scList) - if err != nil { - log.Error(err, "[UpdateFunc] unable to list Storage Classes") - err = updateLocalStorageClassPhase(ctx, cl, newLsc, FailedStatusPhase, fmt.Sprintf("Unable to list storage classes, err: %s", err.Error())) - q.AddAfter(reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: newLsc.Namespace, - Name: newLsc.Name, - }, - }, cfg.RequeueStorageClassInterval*time.Second) - return - } - - shouldRequeue, err := runEventReconcile(ctx, cl, log, scList, newLsc) - if err != nil { - log.Error(err, fmt.Sprintf("[UpdateFunc] an error occured while reconciles the LocalStorageClass, name: %s", newLsc.Name)) - } - - if shouldRequeue { - log.Warning(fmt.Sprintf("[UpdateFunc] the LocalStorageClass %s event will be requeued", newLsc.Name)) - q.AddAfter(reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: newLsc.Namespace, - Name: newLsc.Name, - }, - }, cfg.RequeueStorageClassInterval*time.Second) - } - - log.Info(fmt.Sprintf("[UpdateFunc] ends the reconciliation for the LocalStorageClass %s", e.ObjectNew.GetName())) + log.Info(fmt.Sprintf("[UpdateFunc] the LocalStorageClass %q will be reconciled. Add to the queue", newLsc.Name)) + request := reconcile.Request{NamespacedName: types.NamespacedName{Namespace: newLsc.Namespace, Name: newLsc.Name}} + q.Add(request) }, }) if err != nil { @@ -233,10 +164,14 @@ func RunLocalStorageClassWatcherController( return c, nil } -func runEventReconcile(ctx context.Context, cl client.Client, log logger.Logger, scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) (bool, error) { +func RunEventReconcile(ctx context.Context, cl client.Client, log logger.Logger, scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) (bool, error) { recType, err := identifyReconcileFunc(scList, lsc) if err != nil { - log.Error(err, fmt.Sprintf("[runEventReconcile] unable to identify reconcile func for the LocalStorageClass %s", lsc.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) + if upError != nil { + upError = fmt.Errorf("[runEventReconcile] unable to update the LocalStorageClass %s status: %w", lsc.Name, upError) + err = errors.Join(err, upError) + } return true, err } @@ -257,665 +192,3 @@ func runEventReconcile(ctx context.Context, cl client.Client, log logger.Logger, return false, nil } - -func reconcileLSCDeleteFunc( - ctx context.Context, - cl client.Client, - log logger.Logger, - scList *v1.StorageClassList, - lsc *v1alpha1.LocalStorageClass, -) (bool, error) { - log.Debug(fmt.Sprintf("[reconcileLSCDeleteFunc] tries to find a storage class for the LocalStorageClass %s", lsc.Name)) - var sc *v1.StorageClass - for _, s := range scList.Items { - if s.Name == lsc.Name { - sc = &s - break - } - } - if sc == nil { - log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] no storage class found for the LocalStorageClass, name: %s", lsc.Name)) - } - - if sc != nil { - log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] successfully found a storage class for the LocalStorageClass %s", lsc.Name)) - log.Debug(fmt.Sprintf("[reconcileLSCDeleteFunc] starts identifing a provisioner for the storage class %s", sc.Name)) - - if sc.Provisioner != LocalStorageClassProvisioner { - log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] the storage class %s does not belongs to %s provisioner. It will not be deleted", sc.Name, LocalStorageClassProvisioner)) - } else { - log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] the storage class %s belongs to %s provisioner. It will be deleted", sc.Name, LocalStorageClassProvisioner)) - - err := cl.Delete(ctx, sc) - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCDeleteFunc] unable to delete a storage class, name: %s", sc.Name)) - upErr := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, fmt.Sprintf("Unable to delete a storage class, err: %s", err.Error())) - if upErr != nil { - log.Error(upErr, fmt.Sprintf("[reconcileLSCDeleteFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) - } - return true, err - } - log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] successfully deleted a storage class, name: %s", sc.Name)) - } - } - - log.Debug(fmt.Sprintf("[reconcileLSCDeleteFunc] starts removing a finalizer %s from the LocalStorageClass, name: %s", LocalStorageClassFinalizerName, lsc.Name)) - removed, err := removeLocalSCFinalizerIfExists(ctx, cl, lsc) - if err != nil { - log.Error(err, "[reconcileLSCDeleteFunc] unable to remove a finalizer %s from the LocalStorageClass, name: %s", LocalStorageClassFinalizerName, lsc.Name) - upErr := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, fmt.Sprintf("Unable to remove a finalizer, err: %s", err.Error())) - if upErr != nil { - log.Error(upErr, fmt.Sprintf("[reconcileLSCDeleteFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) - } - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCDeleteFunc] the LocalStorageClass %s finalizer %s was removed: %t", lsc.Name, LocalStorageClassFinalizerName, removed)) - - log.Debug("[reconcileLSCDeleteFunc] ends the reconciliation") - return false, nil -} - -func removeLocalSCFinalizerIfExists(ctx context.Context, cl client.Client, lsc *v1alpha1.LocalStorageClass) (bool, error) { - removed := false - for i, f := range lsc.Finalizers { - if f == LocalStorageClassFinalizerName { - lsc.Finalizers = append(lsc.Finalizers[:i], lsc.Finalizers[i+1:]...) - removed = true - break - } - } - - if removed { - err := cl.Update(ctx, lsc) - if err != nil { - return false, err - } - } - - return removed, nil -} - -func reconcileLSCUpdateFunc( - ctx context.Context, - cl client.Client, - log logger.Logger, - scList *v1.StorageClassList, - lsc *v1alpha1.LocalStorageClass, -) (bool, error) { - log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] starts the LocalStorageClass %s validation", lsc.Name)) - valid, msg := validateLocalStorageClass(ctx, cl, scList, lsc) - if !valid { - err := errors.New("validation failed. Check the resource's Status.Message for more information") - log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] Unable to reconcile the LocalStorageClass, name: %s", lsc.Name)) - upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, msg) - if upError != nil { - log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) - } - - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] successfully validated the LocalStorageClass, name: %s", lsc.Name)) - - var sc *v1.StorageClass - for _, s := range scList.Items { - if s.Name == lsc.Name { - sc = &s - break - } - } - if sc == nil { - err := errors.New(fmt.Sprintf("a storage class %s does not exist", lsc.Name)) - log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to find a storage class for the LocalStorageClass, name: %s", lsc.Name)) - upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) - if upError != nil { - log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) - } - return true, err - } - - log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] successfully found a storage class for the LocalStorageClass, name: %s", lsc.Name)) - - if lsc.Spec.LVM != nil { - log.Trace(fmt.Sprintf("[reconcileLSCUpdateFunc] storage class %s params: %+v", sc.Name, sc.Parameters)) - log.Trace(fmt.Sprintf("[reconcileLSCUpdateFunc] LocalStorageClass %s Spec.LVM: %+v", lsc.Name, lsc.Spec.LVM)) - hasDiff, err := hasLVGDiff(sc, lsc) - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to identify the LVMVolumeGroup difference for the LocalStorageClass %s", lsc.Name)) - upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) - if upError != nil { - log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) - } - return true, err - } - - if hasDiff { - log.Info(fmt.Sprintf("[reconcileLSCUpdateFunc] current Storage Class LVMVolumeGroups do not match LocalStorageClass ones. The Storage Class %s will be recreated with new ones", lsc.Name)) - err = cl.Delete(ctx, sc) - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to delete a Storage Class %s", sc.Name)) - upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) - if upError != nil { - log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) - } - return true, err - } - - sc, err = configureStorageClass(lsc) - err = cl.Create(ctx, sc) - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to create a Storage Class %s", sc.Name)) - upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) - if upError != nil { - log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) - } - return true, err - } - - log.Info(fmt.Sprintf("[reconcileLSCUpdateFunc] a Storage Class %s was successfully recreated", sc.Name)) - err = updateLocalStorageClassPhase(ctx, cl, lsc, CreatedStatusPhase, "") - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] successfully updated the LocalStorageClass %s status", sc.Name)) - - return false, nil - } - } - - sc = patchSCByLSC(sc, lsc) - log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] patched a storage class by the LocalStorageClass, name: %s", lsc.Name)) - - err := cl.Update(ctx, sc) - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update a storage class, name: %s", sc.Name)) - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] successfully updated the storage class, name: %s", sc.Name)) - - err = updateLocalStorageClassPhase(ctx, cl, lsc, CreatedStatusPhase, "") - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] successfully updated the LocalStorageClass %s status", sc.Name)) - - return false, nil -} - -func patchSCByLSC(sc *v1.StorageClass, lsc *v1alpha1.LocalStorageClass) *v1.StorageClass { - - if lsc.Spec.IsDefault { - if sc.Annotations == nil { - sc.Annotations = make(map[string]string) - } - sc.Annotations[DefaultStorageClassAnnotationKey] = "true" - } else { - _, isDefault := sc.Annotations[DefaultStorageClassAnnotationKey] - if isDefault { - delete(sc.Annotations, DefaultStorageClassAnnotationKey) - } - } - - return sc -} - -func identifyReconcileFunc(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) (reconcileType, error) { - should := shouldReconcileByCreateFunc(scList, lsc) - if should { - return CreateReconcile, nil - } - - should, err := shouldReconcileByUpdateFunc(scList, lsc) - if err != nil { - return "none", err - } - if should { - return UpdateReconcile, nil - } - - should = shouldReconcileByDeleteFunc(lsc) - if should { - return DeleteReconcile, nil - } - - return "none", nil -} - -func shouldReconcileByDeleteFunc(lsc *v1alpha1.LocalStorageClass) bool { - if lsc.DeletionTimestamp != nil { - return true - } - - return false -} - -func shouldReconcileByUpdateFunc(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) (bool, error) { - if lsc.DeletionTimestamp != nil { - return false, nil - } - - if lsc.Status.Phase == FailedStatusPhase { - return true, nil - } - - for _, sc := range scList.Items { - if sc.Name == lsc.Name && sc.Provisioner == LocalStorageClassProvisioner { - lscDefault := "false" - if lsc.Spec.IsDefault { - lscDefault = "true" - } - - if sc.Annotations[DefaultStorageClassAnnotationKey] != lscDefault { - return true, nil - } - - diff, err := hasLVGDiff(&sc, lsc) - if err != nil { - return false, err - } - - if diff { - return true, nil - } - } - } - - return false, nil -} - -func hasLVGDiff(sc *v1.StorageClass, lsc *v1alpha1.LocalStorageClass) (bool, error) { - currentLVGs, err := getLVGFromSCParams(sc) - if err != nil { - return false, err - } - - if len(currentLVGs) != len(lsc.Spec.LVM.LVMVolumeGroups) { - return true, nil - } - - for i := range currentLVGs { - if currentLVGs[i].Name != lsc.Spec.LVM.LVMVolumeGroups[i].Name { - return true, nil - } - } - - return false, nil -} - -func getLVGFromSCParams(sc *v1.StorageClass) ([]v1alpha1.LocalStorageClassLVG, error) { - lvgsFromParams := sc.Parameters[LVMVolumeGroupsParamKey] - var currentLVGs []v1alpha1.LocalStorageClassLVG - - err := yaml.Unmarshal([]byte(lvgsFromParams), ¤tLVGs) - if err != nil { - return nil, err - } - - return currentLVGs, nil -} - -func shouldReconcileByCreateFunc(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) bool { - if lsc.DeletionTimestamp != nil { - return false - } - - for _, sc := range scList.Items { - if sc.Name == lsc.Name && - lsc.Status != nil { - return false - } - } - - return true -} - -func reconcileLSCCreateFunc( - ctx context.Context, - cl client.Client, - log logger.Logger, - scList *v1.StorageClassList, - lsc *v1alpha1.LocalStorageClass, -) (bool, error) { - log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] starts the LocalStorageClass %s validation", lsc.Name)) - valid, msg := validateLocalStorageClass(ctx, cl, scList, lsc) - if !valid { - err := errors.New("validation failed. Check the resource's Status.Message for more information") - log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] Unable to reconcile the LocalStorageClass, name: %s", lsc.Name)) - upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, msg) - if upError != nil { - log.Error(upError, fmt.Sprintf("[reconcileLSCCreateFunc] unable to update the LocalStorageClass %s", lsc.Name)) - } - - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] successfully validated the LocalStorageClass, name: %s", lsc.Name)) - - log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] starts storage class configuration for the LocalStorageClass, name: %s", lsc.Name)) - sc, err := configureStorageClass(lsc) - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to configure Storage Class for LocalStorageClass, name: %s", lsc.Name)) - upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) - if upError != nil { - log.Error(upError, fmt.Sprintf("[reconcileLSCCreateFunc] unable to update the LocalStorageClass %s", lsc.Name)) - return true, upError - } - return false, err - } - log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] successfully configurated storage class for the LocalStorageClass, name: %s", lsc.Name)) - - created, err := createStorageClassIfNotExists(ctx, cl, scList, sc) - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to create a Storage Class, name: %s", sc.Name)) - upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) - if upError != nil { - log.Error(upError, fmt.Sprintf("[reconcileLSCCreateFunc] unable to update the LocalStorageClass %s", lsc.Name)) - return true, upError - } - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] a storage class %s was created: %t", sc.Name, created)) - if created { - log.Info(fmt.Sprintf("[reconcileLSCCreateFunc] successfully create storage class, name: %s", sc.Name)) - } - - err = updateLocalStorageClassPhase(ctx, cl, lsc, CreatedStatusPhase, "") - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] successfully updated the LocalStorageClass %s status", sc.Name)) - - added, err := addFinalizerIfNotExists(ctx, cl, lsc) - if err != nil { - log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to add a finalizer %s to the LocalStorageClass %s", LocalStorageClassFinalizerName, lsc.Name)) - return true, err - } - log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] finalizer %s was added to the LocalStorageClass %s: %t", LocalStorageClassFinalizerName, lsc.Name, added)) - - return false, nil -} - -func createStorageClassIfNotExists( - ctx context.Context, - cl client.Client, - scList *v1.StorageClassList, - sc *v1.StorageClass, -) (bool, error) { - for _, s := range scList.Items { - if s.Name == sc.Name { - return false, nil - } - } - - err := cl.Create(ctx, sc) - if err != nil { - return false, err - } - - return true, err -} - -func addFinalizerIfNotExists(ctx context.Context, cl client.Client, lsc *v1alpha1.LocalStorageClass) (bool, error) { - if !slices.Contains(lsc.Finalizers, LocalStorageClassFinalizerName) { - lsc.Finalizers = append(lsc.Finalizers, LocalStorageClassFinalizerName) - } - - err := cl.Update(ctx, lsc) - if err != nil { - return false, err - } - - return true, nil -} - -func configureStorageClass(lsc *v1alpha1.LocalStorageClass) (*v1.StorageClass, error) { - reclaimPolicy := corev1.PersistentVolumeReclaimPolicy(lsc.Spec.ReclaimPolicy) - volumeBindingMode := v1.VolumeBindingMode(lsc.Spec.VolumeBindingMode) - AllowVolumeExpansion := AllowVolumeExpansionDefaultValue - - if lsc.Spec.LVM == nil { - //TODO: add support for other LSC types - return nil, fmt.Errorf("unable to identify the LocalStorageClass type") - } - - lvgsParam, err := yaml.Marshal(lsc.Spec.LVM.LVMVolumeGroups) - if err != nil { - return nil, err - } - - params := map[string]string{ - TypeParamKey: Lvm, - LVMTypeParamKey: lsc.Spec.LVM.Type, - LVMVolumeBindingModeParamKey: lsc.Spec.VolumeBindingMode, - LVMVolumeGroupsParamKey: string(lvgsParam), - } - - isDefault := "false" - if lsc.Spec.IsDefault { - isDefault = "true" - } - - sc := &v1.StorageClass{ - TypeMeta: metav1.TypeMeta{ - Kind: StorageClassKind, - APIVersion: StorageClassAPIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: lsc.Name, - Namespace: lsc.Namespace, - Annotations: map[string]string{ - DefaultStorageClassAnnotationKey: isDefault, - }, - }, - Provisioner: LocalStorageClassProvisioner, - Parameters: params, - ReclaimPolicy: &reclaimPolicy, - AllowVolumeExpansion: &AllowVolumeExpansion, - VolumeBindingMode: &volumeBindingMode, - } - - return sc, nil -} - -func updateLocalStorageClassPhase( - ctx context.Context, - cl client.Client, - lsc *v1alpha1.LocalStorageClass, - phase, - reason string, -) error { - if lsc.Status == nil { - lsc.Status = new(v1alpha1.LocalStorageClassStatus) - } - lsc.Status.Phase = phase - lsc.Status.Reason = reason - - // TODO: add retry logic - err := cl.Update(ctx, lsc) - if err != nil { - return err - } - - return nil -} - -func validateLocalStorageClass( - ctx context.Context, - cl client.Client, - scList *v1.StorageClassList, - lsc *v1alpha1.LocalStorageClass, -) (bool, string) { - var ( - failedMsgBuilder strings.Builder - valid = true - ) - - unmanagedScName := findUnmanagedDuplicatedSC(scList, lsc) - if unmanagedScName != "" { - valid = false - failedMsgBuilder.WriteString(fmt.Sprintf("There already is a storage class with the same name: %s\n", unmanagedScName)) - } - - if lsc.Spec.IsDefault { - exsDefaultSCNames := findOtherDefaultStorageClasses(scList, lsc) - if len(exsDefaultSCNames) != 0 { - valid = false - failedMsgBuilder.WriteString(fmt.Sprintf("There already is a default storage class, name: %s\n", strings.Join(exsDefaultSCNames, ","))) - } - } - - lvgList := &v1alpha1.LvmVolumeGroupList{} - err := cl.List(ctx, lvgList) - if err != nil { - valid = false - failedMsgBuilder.WriteString(fmt.Sprintf("Unable to validate selected LVMVolumeGroups, err: %s\n", err.Error())) - return valid, failedMsgBuilder.String() - } - - if lsc.Spec.LVM != nil { - LVGsFromTheSameNode := findLVMVolumeGroupsOnTheSameNode(lvgList, lsc) - if len(LVGsFromTheSameNode) != 0 { - valid = false - failedMsgBuilder.WriteString(fmt.Sprintf("Some LVMVolumeGroups use the same node (|node: LVG names): %s\n", strings.Join(LVGsFromTheSameNode, ""))) - } - - nonexistentLVGs := findNonexistentLVGs(lvgList, lsc) - if len(nonexistentLVGs) != 0 { - valid = false - failedMsgBuilder.WriteString(fmt.Sprintf("Some of selected LVMVolumeGroups are nonexistent, LVG names: %s\n", strings.Join(nonexistentLVGs, ","))) - } - - if lsc.Spec.LVM.Type == Thin { - LVGSWithNonexistentTps := findNonexistentThinPools(lvgList, lsc) - if len(LVGSWithNonexistentTps) != 0 { - valid = false - failedMsgBuilder.WriteString(fmt.Sprintf("Some LVMVolumeGroups use nonexistent thin pools, LVG names: %s\n", strings.Join(LVGSWithNonexistentTps, ","))) - } - } else { - LVGsWithTps := findAnyThinPool(lsc) - if len(LVGsWithTps) != 0 { - valid = false - failedMsgBuilder.WriteString(fmt.Sprintf("Some LVMVolumeGroups use thin pools though device type is Thick, LVG names: %s\n", strings.Join(LVGsWithTps, ","))) - } - } - } else { - // TODO: add support for other types - valid = false - failedMsgBuilder.WriteString(fmt.Sprintf("Unable to identify a type of LocalStorageClass %s", lsc.Name)) - } - - return valid, failedMsgBuilder.String() -} - -func findUnmanagedDuplicatedSC(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) string { - for _, sc := range scList.Items { - if sc.Name == lsc.Name && sc.Provisioner != LocalStorageClassProvisioner { - return sc.Name - } - } - - return "" -} - -func findAnyThinPool(lsc *v1alpha1.LocalStorageClass) []string { - badLvgs := make([]string, 0, len(lsc.Spec.LVM.LVMVolumeGroups)) - for _, lvs := range lsc.Spec.LVM.LVMVolumeGroups { - if lvs.Thin != nil { - badLvgs = append(badLvgs, lvs.Name) - } - } - - return badLvgs -} - -func findNonexistentThinPools(lvgList *v1alpha1.LvmVolumeGroupList, lsc *v1alpha1.LocalStorageClass) []string { - lvgs := make(map[string]v1alpha1.LvmVolumeGroup, len(lvgList.Items)) - for _, lvg := range lvgList.Items { - lvgs[lvg.Name] = lvg - } - - badLvgs := make([]string, 0, len(lsc.Spec.LVM.LVMVolumeGroups)) - for _, lscLvg := range lsc.Spec.LVM.LVMVolumeGroups { - if lscLvg.Thin == nil { - badLvgs = append(badLvgs, lscLvg.Name) - continue - } - - lvgRes := lvgs[lscLvg.Name] - exist := false - - for _, tp := range lvgRes.Status.ThinPools { - if tp.Name == lscLvg.Thin.PoolName { - exist = true - break - } - } - - if !exist { - badLvgs = append(badLvgs, lscLvg.Name) - } - } - - return badLvgs -} - -func findNonexistentLVGs(lvgList *v1alpha1.LvmVolumeGroupList, lsc *v1alpha1.LocalStorageClass) []string { - lvgs := make(map[string]struct{}, len(lvgList.Items)) - for _, lvg := range lvgList.Items { - lvgs[lvg.Name] = struct{}{} - } - - nonexistent := make([]string, 0, len(lsc.Spec.LVM.LVMVolumeGroups)) - for _, lvg := range lsc.Spec.LVM.LVMVolumeGroups { - if _, exist := lvgs[lvg.Name]; !exist { - nonexistent = append(nonexistent, lvg.Name) - } - } - - return nonexistent -} - -func findLVMVolumeGroupsOnTheSameNode(lvgList *v1alpha1.LvmVolumeGroupList, lsc *v1alpha1.LocalStorageClass) []string { - nodesWithLVGs := make(map[string][]string, len(lsc.Spec.LVM.LVMVolumeGroups)) - usedLVGs := make(map[string]struct{}, len(lsc.Spec.LVM.LVMVolumeGroups)) - for _, lvg := range lsc.Spec.LVM.LVMVolumeGroups { - usedLVGs[lvg.Name] = struct{}{} - } - - badLVGs := make([]string, 0, len(lsc.Spec.LVM.LVMVolumeGroups)) - for _, lvg := range lvgList.Items { - if _, used := usedLVGs[lvg.Name]; used { - for _, node := range lvg.Status.Nodes { - nodesWithLVGs[node.Name] = append(nodesWithLVGs[node.Name], lvg.Name) - } - } - } - - for nodeName, lvgs := range nodesWithLVGs { - if len(lvgs) > 1 { - var msgBuilder strings.Builder - msgBuilder.WriteString(fmt.Sprintf("|%s: ", nodeName)) - for _, lvgName := range lvgs { - msgBuilder.WriteString(fmt.Sprintf("%s,", lvgName)) - } - - badLVGs = append(badLVGs, msgBuilder.String()) - } - } - - return badLVGs -} - -func findOtherDefaultStorageClasses(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) []string { - defaults := make([]string, 0, len(scList.Items)) - - for _, sc := range scList.Items { - isDefault := sc.Annotations[DefaultStorageClassAnnotationKey] - if isDefault == "true" && sc.Name != lsc.Name { - defaults = append(defaults, sc.Name) - } - } - - return defaults -} diff --git a/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher_func.go b/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher_func.go new file mode 100644 index 00000000..f1d881a1 --- /dev/null +++ b/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher_func.go @@ -0,0 +1,705 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "fmt" + v1alpha1 "sds-local-volume-controller/api/v1alpha1" + "sds-local-volume-controller/pkg/logger" + "strings" + + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/strings/slices" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" +) + +func reconcileLSCDeleteFunc( + ctx context.Context, + cl client.Client, + log logger.Logger, + scList *v1.StorageClassList, + lsc *v1alpha1.LocalStorageClass, +) (bool, error) { + log.Debug(fmt.Sprintf("[reconcileLSCDeleteFunc] tries to find a storage class for the LocalStorageClass %s", lsc.Name)) + var sc *v1.StorageClass + for _, s := range scList.Items { + if s.Name == lsc.Name { + sc = &s + break + } + } + if sc == nil { + log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] no storage class found for the LocalStorageClass, name: %s", lsc.Name)) + } + + if sc != nil { + log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] successfully found a storage class for the LocalStorageClass %s", lsc.Name)) + log.Debug(fmt.Sprintf("[reconcileLSCDeleteFunc] starts identifing a provisioner for the storage class %s", sc.Name)) + + if sc.Provisioner != LocalStorageClassProvisioner { + log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] the storage class %s does not belongs to %s provisioner. It will not be deleted", sc.Name, LocalStorageClassProvisioner)) + } else { + log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] the storage class %s belongs to %s provisioner. It will be deleted", sc.Name, LocalStorageClassProvisioner)) + + err := deleteStorageClass(ctx, cl, sc) + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCDeleteFunc] unable to delete a storage class, name: %s", sc.Name)) + upErr := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, fmt.Sprintf("Unable to delete a storage class, err: %s", err.Error())) + if upErr != nil { + log.Error(upErr, fmt.Sprintf("[reconcileLSCDeleteFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) + } + return true, err + } + log.Info(fmt.Sprintf("[reconcileLSCDeleteFunc] successfully deleted a storage class, name: %s", sc.Name)) + } + } + + log.Debug(fmt.Sprintf("[reconcileLSCDeleteFunc] starts removing a finalizer %s from the LocalStorageClass, name: %s", LocalStorageClassFinalizerName, lsc.Name)) + removed, err := removeFinalizerIfExists(ctx, cl, lsc, LocalStorageClassFinalizerName) + if err != nil { + log.Error(err, "[reconcileLSCDeleteFunc] unable to remove a finalizer %s from the LocalStorageClass, name: %s", LocalStorageClassFinalizerName, lsc.Name) + upErr := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, fmt.Sprintf("Unable to remove a finalizer, err: %s", err.Error())) + if upErr != nil { + log.Error(upErr, fmt.Sprintf("[reconcileLSCDeleteFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) + } + return true, err + } + log.Debug(fmt.Sprintf("[reconcileLSCDeleteFunc] the LocalStorageClass %s finalizer %s was removed: %t", lsc.Name, LocalStorageClassFinalizerName, removed)) + + log.Debug("[reconcileLSCDeleteFunc] ends the reconciliation") + return false, nil +} + +func reconcileLSCUpdateFunc( + ctx context.Context, + cl client.Client, + log logger.Logger, + scList *v1.StorageClassList, + lsc *v1alpha1.LocalStorageClass, +) (bool, error) { + log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] starts the LocalStorageClass %s validation", lsc.Name)) + valid, msg := validateLocalStorageClass(ctx, cl, scList, lsc) + if !valid { + err := fmt.Errorf("validation failed: %s", msg) + log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] Unable to reconcile the LocalStorageClass, name: %s", lsc.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, msg) + if upError != nil { + log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) + } + + return true, err + } + log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] successfully validated the LocalStorageClass, name: %s", lsc.Name)) + + var oldSC *v1.StorageClass + for _, s := range scList.Items { + if s.Name == lsc.Name { + oldSC = &s + break + } + } + if oldSC == nil { + err := fmt.Errorf("a storage class %s does not exist", lsc.Name) + log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to find a storage class for the LocalStorageClass, name: %s", lsc.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) + if upError != nil { + log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) + } + return true, err + } + + log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] successfully found a storage class for the LocalStorageClass, name: %s", lsc.Name)) + + log.Trace(fmt.Sprintf("[reconcileLSCUpdateFunc] storage class %s params: %+v", oldSC.Name, oldSC.Parameters)) + log.Trace(fmt.Sprintf("[reconcileLSCUpdateFunc] LocalStorageClass %s Spec.LVM: %+v", lsc.Name, lsc.Spec.LVM)) + hasDiff, err := hasLVGDiff(oldSC, lsc) + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to identify the LVMVolumeGroup difference for the LocalStorageClass %s", lsc.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) + if upError != nil { + log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) + } + return true, err + } + + if hasDiff { + log.Info(fmt.Sprintf("[reconcileLSCUpdateFunc] current Storage Class LVMVolumeGroups do not match LocalStorageClass ones. The Storage Class %s will be recreated with new ones", lsc.Name)) + newSC, err := configureStorageClass(lsc) + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to configure a Storage Class for the LocalStorageClass %s", lsc.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) + if upError != nil { + log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) + return true, upError + } + return false, err + } + + err = recreateStorageClass(ctx, cl, oldSC, newSC) + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to recreate a Storage Class %s", newSC.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) + if upError != nil { + log.Error(upError, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass %s", lsc.Name)) + } + return true, err + } + + log.Info(fmt.Sprintf("[reconcileLSCUpdateFunc] a Storage Class %s was successfully recreated", newSC.Name)) + } + + err = updateLocalStorageClassPhase(ctx, cl, lsc, CreatedStatusPhase, "") + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCUpdateFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) + return true, err + } + log.Debug(fmt.Sprintf("[reconcileLSCUpdateFunc] successfully updated the LocalStorageClass %s status", lsc.Name)) + + return false, nil +} + +func identifyReconcileFunc(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) (reconcileType, error) { + if shouldReconcileByDeleteFunc(lsc) { + return DeleteReconcile, nil + } + + if shouldReconcileByCreateFunc(scList, lsc) { + return CreateReconcile, nil + } + + should, err := shouldReconcileByUpdateFunc(scList, lsc) + if err != nil { + return "none", err + } + if should { + return UpdateReconcile, nil + } + + return "none", nil +} + +func shouldReconcileByDeleteFunc(lsc *v1alpha1.LocalStorageClass) bool { + if lsc.DeletionTimestamp != nil { + return true + } + + return false +} + +func shouldReconcileByUpdateFunc(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) (bool, error) { + if lsc.DeletionTimestamp != nil { + return false, nil + } + + for _, sc := range scList.Items { + if sc.Name == lsc.Name { + if sc.Provisioner == LocalStorageClassProvisioner { + diff, err := hasLVGDiff(&sc, lsc) + if err != nil { + return false, err + } + + if diff { + return true, nil + } + + if lsc.Status.Phase == FailedStatusPhase { + return true, nil + } + + return false, nil + + } else { + err := fmt.Errorf("a storage class %s already exists and does not belong to %s provisioner", sc.Name, LocalStorageClassProvisioner) + return false, err + } + } + } + + err := fmt.Errorf("a storage class %s does not exist", lsc.Name) + return false, err + +} + +func hasLVGDiff(sc *v1.StorageClass, lsc *v1alpha1.LocalStorageClass) (bool, error) { + currentLVGs, err := getLVGFromSCParams(sc) + if err != nil { + return false, err + } + + if len(currentLVGs) != len(lsc.Spec.LVM.LVMVolumeGroups) { + return true, nil + } + + for i := range currentLVGs { + if currentLVGs[i].Name != lsc.Spec.LVM.LVMVolumeGroups[i].Name { + return true, nil + } + if lsc.Spec.LVM.Type == LVMThinType { + if currentLVGs[i].Thin == nil && lsc.Spec.LVM.LVMVolumeGroups[i].Thin != nil { + return true, nil + } + if currentLVGs[i].Thin == nil && lsc.Spec.LVM.LVMVolumeGroups[i].Thin == nil { + err := fmt.Errorf("LocalStorageClass type=%q: unable to identify the Thin pool differences for the LocalStorageClass %q. The current LVMVolumeGroup %q does not have a Thin pool configured in either the StorageClass or the LocalStorageClass", lsc.Spec.LVM.Type, lsc.Name, currentLVGs[i].Name) + return false, err + } + if currentLVGs[i].Thin.PoolName != lsc.Spec.LVM.LVMVolumeGroups[i].Thin.PoolName { + return true, nil + } + } + } + + return false, nil +} + +func getLVGFromSCParams(sc *v1.StorageClass) ([]v1alpha1.LocalStorageClassLVG, error) { + lvgsFromParams := sc.Parameters[LVMVolumeGroupsParamKey] + var currentLVGs []v1alpha1.LocalStorageClassLVG + + err := yaml.Unmarshal([]byte(lvgsFromParams), ¤tLVGs) + if err != nil { + return nil, err + } + + return currentLVGs, nil +} + +func shouldReconcileByCreateFunc(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) bool { + if lsc.DeletionTimestamp != nil { + return false + } + + for _, sc := range scList.Items { + if sc.Name == lsc.Name { + return false + } + } + + return true +} + +func reconcileLSCCreateFunc( + ctx context.Context, + cl client.Client, + log logger.Logger, + scList *v1.StorageClassList, + lsc *v1alpha1.LocalStorageClass, +) (bool, error) { + log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] starts the LocalStorageClass %s validation", lsc.Name)) + added, err := addFinalizerIfNotExistsForLSC(ctx, cl, lsc) + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to add a finalizer %s to the LocalStorageClass %s", LocalStorageClassFinalizerName, lsc.Name)) + return true, err + } + log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] finalizer %s was added to the LocalStorageClass %s: %t", LocalStorageClassFinalizerName, lsc.Name, added)) + + valid, msg := validateLocalStorageClass(ctx, cl, scList, lsc) + if !valid { + err := fmt.Errorf("validation failed: %s", msg) + log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] Unable to reconcile the LocalStorageClass, name: %s", lsc.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, msg) + if upError != nil { + log.Error(upError, fmt.Sprintf("[reconcileLSCCreateFunc] unable to update the LocalStorageClass %s", lsc.Name)) + } + + return true, err + } + log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] successfully validated the LocalStorageClass, name: %s", lsc.Name)) + + log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] starts storage class configuration for the LocalStorageClass, name: %s", lsc.Name)) + sc, err := configureStorageClass(lsc) + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to configure Storage Class for LocalStorageClass, name: %s", lsc.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) + if upError != nil { + log.Error(upError, fmt.Sprintf("[reconcileLSCCreateFunc] unable to update the LocalStorageClass %s", lsc.Name)) + return true, upError + } + return false, err + } + log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] successfully configurated storage class for the LocalStorageClass, name: %s", lsc.Name)) + + created, err := createStorageClassIfNotExists(ctx, cl, scList, sc) + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to create a Storage Class, name: %s", sc.Name)) + upError := updateLocalStorageClassPhase(ctx, cl, lsc, FailedStatusPhase, err.Error()) + if upError != nil { + log.Error(upError, fmt.Sprintf("[reconcileLSCCreateFunc] unable to update the LocalStorageClass %s", lsc.Name)) + return true, upError + } + return true, err + } + log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] a storage class %s was created: %t", sc.Name, created)) + if created { + log.Info(fmt.Sprintf("[reconcileLSCCreateFunc] successfully create storage class, name: %s", sc.Name)) + } else { + log.Warning(fmt.Sprintf("[reconcileLSCCreateFunc] Storage class %s already exists. Adding event to requeue.", sc.Name)) + return true, nil + } + + added, err = addFinalizerIfNotExistsForSC(ctx, cl, sc) + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to add a finalizer %s to the StorageClass %s", LocalStorageClassFinalizerName, sc.Name)) + return true, err + } + log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] finalizer %s was added to the StorageClass %s: %t", LocalStorageClassFinalizerName, sc.Name, added)) + + err = updateLocalStorageClassPhase(ctx, cl, lsc, CreatedStatusPhase, "") + if err != nil { + log.Error(err, fmt.Sprintf("[reconcileLSCCreateFunc] unable to update the LocalStorageClass, name: %s", lsc.Name)) + return true, err + } + log.Debug(fmt.Sprintf("[reconcileLSCCreateFunc] successfully updated the LocalStorageClass %s status", sc.Name)) + + return false, nil +} + +func createStorageClassIfNotExists( + ctx context.Context, + cl client.Client, + scList *v1.StorageClassList, + sc *v1.StorageClass, +) (bool, error) { + for _, s := range scList.Items { + if s.Name == sc.Name { + return false, nil + } + } + + err := cl.Create(ctx, sc) + if err != nil { + return false, err + } + + return true, err +} + +func addFinalizerIfNotExistsForLSC(ctx context.Context, cl client.Client, lsc *v1alpha1.LocalStorageClass) (bool, error) { + if !slices.Contains(lsc.Finalizers, LocalStorageClassFinalizerName) { + lsc.Finalizers = append(lsc.Finalizers, LocalStorageClassFinalizerName) + } + + err := cl.Update(ctx, lsc) + if err != nil { + return false, err + } + + return true, nil +} + +func addFinalizerIfNotExistsForSC(ctx context.Context, cl client.Client, sc *v1.StorageClass) (bool, error) { + if !slices.Contains(sc.Finalizers, LocalStorageClassFinalizerName) { + sc.Finalizers = append(sc.Finalizers, LocalStorageClassFinalizerName) + } + + err := cl.Update(ctx, sc) + if err != nil { + return false, err + } + + return true, nil +} + +func configureStorageClass(lsc *v1alpha1.LocalStorageClass) (*v1.StorageClass, error) { + reclaimPolicy := corev1.PersistentVolumeReclaimPolicy(lsc.Spec.ReclaimPolicy) + volumeBindingMode := v1.VolumeBindingMode(lsc.Spec.VolumeBindingMode) + AllowVolumeExpansion := AllowVolumeExpansionDefaultValue + + if lsc.Spec.LVM == nil { + //TODO: add support for other LSC types + return nil, fmt.Errorf("unable to identify the LocalStorageClass type") + } + + lvgsParam, err := yaml.Marshal(lsc.Spec.LVM.LVMVolumeGroups) + if err != nil { + return nil, err + } + + params := map[string]string{ + TypeParamKey: LocalStorageClassLvmType, + LVMTypeParamKey: lsc.Spec.LVM.Type, + LVMVolumeBindingModeParamKey: lsc.Spec.VolumeBindingMode, + LVMVolumeGroupsParamKey: string(lvgsParam), + } + + sc := &v1.StorageClass{ + TypeMeta: metav1.TypeMeta{ + Kind: StorageClassKind, + APIVersion: StorageClassAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: lsc.Name, + Namespace: lsc.Namespace, + Finalizers: []string{LocalStorageClassFinalizerName}, + }, + Provisioner: LocalStorageClassProvisioner, + Parameters: params, + ReclaimPolicy: &reclaimPolicy, + AllowVolumeExpansion: &AllowVolumeExpansion, + VolumeBindingMode: &volumeBindingMode, + } + + return sc, nil +} + +func updateLocalStorageClassPhase( + ctx context.Context, + cl client.Client, + lsc *v1alpha1.LocalStorageClass, + phase, + reason string, +) error { + if lsc.Status == nil { + lsc.Status = new(v1alpha1.LocalStorageClassStatus) + } + lsc.Status.Phase = phase + lsc.Status.Reason = reason + + if !slices.Contains(lsc.Finalizers, LocalStorageClassFinalizerName) { + lsc.Finalizers = append(lsc.Finalizers, LocalStorageClassFinalizerName) + } + + // TODO: add retry logic + err := cl.Update(ctx, lsc) + if err != nil { + return err + } + + return nil +} + +func validateLocalStorageClass( + ctx context.Context, + cl client.Client, + scList *v1.StorageClassList, + lsc *v1alpha1.LocalStorageClass, +) (bool, string) { + var ( + failedMsgBuilder strings.Builder + valid = true + ) + + unmanagedScName := findUnmanagedDuplicatedSC(scList, lsc) + if unmanagedScName != "" { + valid = false + failedMsgBuilder.WriteString(fmt.Sprintf("There already is a storage class with the same name: %s but it is not managed by the LocalStorageClass controller\n", unmanagedScName)) + } + + lvgList := &v1alpha1.LvmVolumeGroupList{} + err := cl.List(ctx, lvgList) + if err != nil { + valid = false + failedMsgBuilder.WriteString(fmt.Sprintf("Unable to validate selected LVMVolumeGroups, err: %s\n", err.Error())) + return valid, failedMsgBuilder.String() + } + + if lsc.Spec.LVM != nil { + LVGsFromTheSameNode := findLVMVolumeGroupsOnTheSameNode(lvgList, lsc) + if len(LVGsFromTheSameNode) != 0 { + valid = false + failedMsgBuilder.WriteString(fmt.Sprintf("Some LVMVolumeGroups use the same node (|node: LVG names): %s\n", strings.Join(LVGsFromTheSameNode, ""))) + } + + nonexistentLVGs := findNonexistentLVGs(lvgList, lsc) + if len(nonexistentLVGs) != 0 { + valid = false + failedMsgBuilder.WriteString(fmt.Sprintf("Some of selected LVMVolumeGroups are nonexistent, LVG names: %s\n", strings.Join(nonexistentLVGs, ","))) + } + + if lsc.Spec.LVM.Type == LVMThinType { + LVGSWithNonexistentTps := findNonexistentThinPools(lvgList, lsc) + if len(LVGSWithNonexistentTps) != 0 { + valid = false + failedMsgBuilder.WriteString(fmt.Sprintf("Some LVMVolumeGroups use nonexistent thin pools, LVG names: %s\n", strings.Join(LVGSWithNonexistentTps, ","))) + } + } else { + LVGsWithTps := findAnyThinPool(lsc) + if len(LVGsWithTps) != 0 { + valid = false + failedMsgBuilder.WriteString(fmt.Sprintf("Some LVMVolumeGroups use thin pools though device type is Thick, LVG names: %s\n", strings.Join(LVGsWithTps, ","))) + } + } + } else { + // TODO: add support for other types + valid = false + failedMsgBuilder.WriteString(fmt.Sprintf("Unable to identify a type of LocalStorageClass %s", lsc.Name)) + } + + return valid, failedMsgBuilder.String() +} + +func findUnmanagedDuplicatedSC(scList *v1.StorageClassList, lsc *v1alpha1.LocalStorageClass) string { + for _, sc := range scList.Items { + if sc.Name == lsc.Name && sc.Provisioner != LocalStorageClassProvisioner { + return sc.Name + } + } + + return "" +} + +func findAnyThinPool(lsc *v1alpha1.LocalStorageClass) []string { + badLvgs := make([]string, 0, len(lsc.Spec.LVM.LVMVolumeGroups)) + for _, lvs := range lsc.Spec.LVM.LVMVolumeGroups { + if lvs.Thin != nil { + badLvgs = append(badLvgs, lvs.Name) + } + } + + return badLvgs +} + +func findNonexistentThinPools(lvgList *v1alpha1.LvmVolumeGroupList, lsc *v1alpha1.LocalStorageClass) []string { + lvgs := make(map[string]v1alpha1.LvmVolumeGroup, len(lvgList.Items)) + for _, lvg := range lvgList.Items { + lvgs[lvg.Name] = lvg + } + + badLvgs := make([]string, 0, len(lsc.Spec.LVM.LVMVolumeGroups)) + for _, lscLvg := range lsc.Spec.LVM.LVMVolumeGroups { + if lscLvg.Thin == nil { + badLvgs = append(badLvgs, lscLvg.Name) + continue + } + + lvgRes := lvgs[lscLvg.Name] + exist := false + + for _, tp := range lvgRes.Status.ThinPools { + if tp.Name == lscLvg.Thin.PoolName { + exist = true + break + } + } + + if !exist { + badLvgs = append(badLvgs, lscLvg.Name) + } + } + + return badLvgs +} + +func findNonexistentLVGs(lvgList *v1alpha1.LvmVolumeGroupList, lsc *v1alpha1.LocalStorageClass) []string { + lvgs := make(map[string]struct{}, len(lvgList.Items)) + for _, lvg := range lvgList.Items { + lvgs[lvg.Name] = struct{}{} + } + + nonexistent := make([]string, 0, len(lsc.Spec.LVM.LVMVolumeGroups)) + for _, lvg := range lsc.Spec.LVM.LVMVolumeGroups { + if _, exist := lvgs[lvg.Name]; !exist { + nonexistent = append(nonexistent, lvg.Name) + } + } + + return nonexistent +} + +func findLVMVolumeGroupsOnTheSameNode(lvgList *v1alpha1.LvmVolumeGroupList, lsc *v1alpha1.LocalStorageClass) []string { + nodesWithLVGs := make(map[string][]string, len(lsc.Spec.LVM.LVMVolumeGroups)) + usedLVGs := make(map[string]struct{}, len(lsc.Spec.LVM.LVMVolumeGroups)) + for _, lvg := range lsc.Spec.LVM.LVMVolumeGroups { + usedLVGs[lvg.Name] = struct{}{} + } + + badLVGs := make([]string, 0, len(lsc.Spec.LVM.LVMVolumeGroups)) + for _, lvg := range lvgList.Items { + if _, used := usedLVGs[lvg.Name]; used { + for _, node := range lvg.Status.Nodes { + nodesWithLVGs[node.Name] = append(nodesWithLVGs[node.Name], lvg.Name) + } + } + } + + for nodeName, lvgs := range nodesWithLVGs { + if len(lvgs) > 1 { + var msgBuilder strings.Builder + msgBuilder.WriteString(fmt.Sprintf("|%s: ", nodeName)) + for _, lvgName := range lvgs { + msgBuilder.WriteString(fmt.Sprintf("%s,", lvgName)) + } + + badLVGs = append(badLVGs, msgBuilder.String()) + } + } + + return badLVGs +} + +func recreateStorageClass(ctx context.Context, cl client.Client, oldSC, newSC *v1.StorageClass) error { + // It is necessary to pass the original StorageClass to the delete operation because + // the deletion will not succeed if the fields in the StorageClass provided to delete + // differ from those currently in the cluster. + err := deleteStorageClass(ctx, cl, oldSC) + if err != nil { + err = fmt.Errorf("[recreateStorageClass] unable to delete a storage class %s: %s", oldSC.Name, err.Error()) + return err + } + + err = cl.Create(ctx, newSC) + if err != nil { + err = fmt.Errorf("[recreateStorageClass] unable to create a storage class %s: %s", newSC.Name, err.Error()) + return err + } + + return nil +} + +func deleteStorageClass(ctx context.Context, cl client.Client, sc *v1.StorageClass) error { + if sc.Provisioner != LocalStorageClassProvisioner { + return fmt.Errorf("a storage class %s does not belong to %s provisioner", sc.Name, LocalStorageClassProvisioner) + } + + _, err := removeFinalizerIfExists(ctx, cl, sc, LocalStorageClassFinalizerName) + if err != nil { + return err + } + + err = cl.Delete(ctx, sc) + if err != nil { + return err + } + + return nil +} + +func removeFinalizerIfExists(ctx context.Context, cl client.Client, obj metav1.Object, finalizerName string) (bool, error) { + removed := false + finalizers := obj.GetFinalizers() + for i, f := range finalizers { + if f == finalizerName || f == LocalStorageClassFinalizerNameOld { + finalizers = append(finalizers[:i], finalizers[i+1:]...) + removed = true + break + } + } + + if removed { + obj.SetFinalizers(finalizers) + err := cl.Update(ctx, obj.(client.Object)) + if err != nil { + return false, err + } + } + + return removed, nil +} diff --git a/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher_test.go b/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher_test.go new file mode 100644 index 00000000..4a510703 --- /dev/null +++ b/images/sds-local-volume-controller/pkg/controller/local_storage_class_watcher_test.go @@ -0,0 +1,814 @@ +/* +Copyright 2023 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller_test + +import ( + "context" + v1alpha1 "sds-local-volume-controller/api/v1alpha1" + "sds-local-volume-controller/pkg/controller" + "sds-local-volume-controller/pkg/logger" + "slices" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/storage/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ = Describe(controller.LocalStorageClassCtrlName, func() { + const ( + controllerNamespace = "test-namespace" + nameForLocalStorageClass = "sds-local-volume-storage-class" + + existingThickLVG1Name = "test-thick-vg1" + existingThickLVG2Name = "test-thick-vg2" + newThickLVGName = "test-thick-vg3-new" + + existingThinLVG1Name = "test-thin-vg1" + existingThinLVG2Name = "test-thin-vg2" + newThinLVGName = "test-thin-vg3-new" + + nonExistentLVG1Name = "test-vg4-non-existent" + nonExistentLVG2Name = "test-vg5-non-existent" + ) + + var ( + ctx = context.Background() + cl = NewFakeClient() + log = logger.Logger{} + + reclaimPolicyDelete = string(corev1.PersistentVolumeReclaimDelete) + reclaimPolicyRetain = string(corev1.PersistentVolumeReclaimRetain) + + volumeBindingModeWFFC = string(v1.VolumeBindingWaitForFirstConsumer) + volumeBindingModeIM = string(v1.VolumeBindingImmediate) + + existingThickLVG1Template = generateLVMVolumeGroup(existingThickLVG1Name, []string{"dev-1111", "dev-2222"}, []string{}) + existingThickLVG2Template = generateLVMVolumeGroup(existingThickLVG2Name, []string{"dev-3333", "dev-4444"}, []string{}) + newThickLVGTemplate = generateLVMVolumeGroup(newThickLVGName, []string{"dev-5555", "dev-6666"}, []string{}) + + existingThinLVG1Template = generateLVMVolumeGroup(existingThinLVG1Name, []string{"dev-7777", "dev-8888"}, []string{"thin-pool-1", "thin-pool-2"}) + existingThinLVG2Template = generateLVMVolumeGroup(existingThinLVG2Name, []string{"dev-9999", "dev-1010"}, []string{"thin-pool-1", "thin-pool-2"}) + newThinLVGTemplate = generateLVMVolumeGroup(newThinLVGName, []string{"dev-1111", "dev-1212"}, []string{"thin-pool-1", "thin-pool-2"}) + ) + + It("Create_local_sc_with_existing_lvgs", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + } + + err := cl.Create(ctx, existingThickLVG1Template) + Expect(err).NotTo(HaveOccurred()) + + err = cl.Create(ctx, existingThickLVG2Template) + Expect(err).NotTo(HaveOccurred()) + + lscTemplate := generateLocalStorageClass(nameForLocalStorageClass, reclaimPolicyDelete, volumeBindingModeWFFC, controller.LVMThickType, lvgSpec) + + err = cl.Create(ctx, lscTemplate) + Expect(err).NotTo(HaveOccurred()) + + lsc := &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + Expect(lsc).NotTo(BeNil()) + Expect(lsc.Name).To(Equal(nameForLocalStorageClass)) + Expect(lsc.Finalizers).To(HaveLen(0)) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpec, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThickType, reclaimPolicyDelete, volumeBindingModeWFFC) + }) + + It("Update_local_sc_add_existing_lvg", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + {Name: newThickLVGName}, + } + + err := cl.Create(ctx, newThickLVGTemplate) + Expect(err).NotTo(HaveOccurred()) + + lsc := &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc.Spec.LVM.LVMVolumeGroups = append(lsc.Spec.LVM.LVMVolumeGroups, v1alpha1.LocalStorageClassLVG{Name: newThickLVGName}) + + err = cl.Update(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + Expect(lsc.Status.Phase).To(Equal(controller.CreatedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpec, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThickType, reclaimPolicyDelete, volumeBindingModeWFFC) + }) + + It("Update_local_sc_remove_existing_lvg", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc.Spec.LVM.LVMVolumeGroups = delFromSlice(lsc.Spec.LVM.LVMVolumeGroups, newThickLVGName) + + err = cl.Update(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + Expect(lsc.Status.Phase).To(Equal(controller.CreatedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpec, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThickType, reclaimPolicyDelete, volumeBindingModeWFFC) + }) + + It("Update_local_sc_add_non_existing_lvg", func() { + lvgSpecOld := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + } + + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + {Name: nonExistentLVG1Name}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc.Spec.LVM.LVMVolumeGroups = append(lsc.Spec.LVM.LVMVolumeGroups, v1alpha1.LocalStorageClassLVG{Name: nonExistentLVG1Name}) + + err = cl.Update(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).To(HaveOccurred()) + Expect(shouldRequeue).To(BeTrue()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + Expect(lsc.Status.Phase).To(Equal(controller.FailedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpecOld, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThickType, reclaimPolicyDelete, volumeBindingModeWFFC) + }) + + It("Remove_local_sc_with_non_existing_lvg", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + {Name: nonExistentLVG1Name}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + + err = cl.Delete(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc = &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + Expect(scList.Items).To(HaveLen(1)) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + }) + + It("Create_local_sc_with_non_existing_lvgs", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: nonExistentLVG1Name}, + {Name: nonExistentLVG2Name}, + } + + lscTemplate := generateLocalStorageClass(nameForLocalStorageClass, reclaimPolicyDelete, volumeBindingModeWFFC, controller.LVMThickType, lvgSpec) + + err := cl.Create(ctx, lscTemplate) + Expect(err).NotTo(HaveOccurred()) + + lsc := &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + Expect(scList.Items).To(HaveLen(0)) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).To(HaveOccurred()) + Expect(shouldRequeue).To(BeTrue()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Status.Phase).To(Equal(controller.FailedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + + }) + + It("Update_local_sc_with_all_existing_lvgs", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc.Spec.LVM.LVMVolumeGroups = lvgSpec + + err = cl.Update(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + Expect(scList.Items).To(HaveLen(0)) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + Expect(lsc.Status.Phase).To(Equal(controller.CreatedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpec, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThickType, reclaimPolicyDelete, volumeBindingModeWFFC) + }) + + It("Remove_local_sc_with_existing_lvgs", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + err = cl.Delete(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc = &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + Expect(scList.Items).To(HaveLen(1)) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + }) + + It("Create_local_sc_when_sc_with_another_provisioner_exists", func() { + sc := &v1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: nameForLocalStorageClass, + }, + Provisioner: "test-provisioner", + } + + err := cl.Create(ctx, sc) + Expect(err).NotTo(HaveOccurred()) + + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + } + + lscTemplate := generateLocalStorageClass(nameForLocalStorageClass, reclaimPolicyDelete, volumeBindingModeWFFC, controller.LVMThickType, lvgSpec) + + err = cl.Create(ctx, lscTemplate) + Expect(err).NotTo(HaveOccurred()) + + lsc := &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + Expect(scList.Items).To(HaveLen(1)) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).To(HaveOccurred()) + Expect(shouldRequeue).To(BeTrue()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Status.Phase).To(Equal(controller.FailedStatusPhase)) + + sc = &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + Expect(sc.Provisioner).To(Equal("test-provisioner")) + Expect(sc.Finalizers).To(HaveLen(0)) + }) + + It("Update_local_sc_add_existing_vg_when_sc_with_another_provisioner_exists", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + {Name: newThickLVGName}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc.Spec.LVM.LVMVolumeGroups = append(lsc.Spec.LVM.LVMVolumeGroups, v1alpha1.LocalStorageClassLVG{Name: newThickLVGName}) + + err = cl.Update(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + Expect(scList.Items).To(HaveLen(1)) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).To(HaveOccurred()) + Expect(shouldRequeue).To(BeTrue()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + Expect(lsc.Status.Phase).To(Equal(controller.FailedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + Expect(sc.Provisioner).To(Equal("test-provisioner")) + Expect(sc.Finalizers).To(HaveLen(0)) + }) + + It("Remove_local_sc_with_existing_vgs_when_sc_with_another_provisioner_exists", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThickLVG1Name}, + {Name: existingThickLVG2Name}, + {Name: newThickLVGName}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + err = cl.Delete(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc = &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + Expect(scList.Items).To(HaveLen(1)) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + Expect(sc.Provisioner).To(Equal("test-provisioner")) + Expect(sc.Finalizers).To(HaveLen(0)) + + err = cl.Delete(ctx, sc) + Expect(err).NotTo(HaveOccurred()) + + sc = &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + }) + + It("Create_local_thin_sc_with_existing_thin_lvgs", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThinLVG1Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-1"}}, + {Name: existingThinLVG2Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-2"}}, + } + + err := cl.Create(ctx, existingThinLVG1Template) + Expect(err).NotTo(HaveOccurred()) + + err = cl.Create(ctx, existingThinLVG2Template) + Expect(err).NotTo(HaveOccurred()) + + lscTemplate := generateLocalStorageClass(nameForLocalStorageClass, reclaimPolicyRetain, volumeBindingModeIM, controller.LVMThinType, lvgSpec) + + err = cl.Create(ctx, lscTemplate) + Expect(err).NotTo(HaveOccurred()) + + lsc := &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + Expect(lsc).NotTo(BeNil()) + Expect(lsc.Name).To(Equal(nameForLocalStorageClass)) + Expect(lsc.Finalizers).To(HaveLen(0)) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpec, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThinType, reclaimPolicyRetain, volumeBindingModeIM) + }) + + It("Update_local_thin_sc_add_existing_thin_lvg", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThinLVG1Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-1"}}, + {Name: existingThinLVG2Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-2"}}, + {Name: newThinLVGName, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-1"}}, + } + + err := cl.Create(ctx, newThinLVGTemplate) + Expect(err).NotTo(HaveOccurred()) + + lsc := &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc.Spec.LVM.LVMVolumeGroups = append(lsc.Spec.LVM.LVMVolumeGroups, v1alpha1.LocalStorageClassLVG{Name: newThinLVGName, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-1"}}) + + err = cl.Update(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + Expect(lsc.Status.Phase).To(Equal(controller.CreatedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpec, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThinType, reclaimPolicyRetain, volumeBindingModeIM) + }) + + It("Update_local_thin_sc_remove_existing_thin_lvg", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThinLVG1Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-1"}}, + {Name: existingThinLVG2Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-2"}}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc.Spec.LVM.LVMVolumeGroups = delFromSlice(lsc.Spec.LVM.LVMVolumeGroups, newThinLVGName) + + err = cl.Update(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + Expect(lsc.Status.Phase).To(Equal(controller.CreatedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpec, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThinType, reclaimPolicyRetain, volumeBindingModeIM) + }) + + It("Update_local_thin_sc_add_existing_thick_lvg", func() { + lvgSpecOld := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThinLVG1Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-1"}}, + {Name: existingThinLVG2Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-2"}}, + } + + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThinLVG1Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-1"}}, + {Name: existingThinLVG2Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-2"}}, + {Name: existingThickLVG1Name}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc.Spec.LVM.LVMVolumeGroups = append(lsc.Spec.LVM.LVMVolumeGroups, v1alpha1.LocalStorageClassLVG{Name: existingThickLVG1Name}) + + err = cl.Update(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).To(HaveOccurred()) + Expect(shouldRequeue).To(BeTrue()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + Expect(lsc.Status.Phase).To(Equal(controller.FailedStatusPhase)) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(err).NotTo(HaveOccurred()) + performStandartChecksForSC(sc, lvgSpecOld, nameForLocalStorageClass, controller.LocalStorageClassLvmType, controller.LVMThinType, reclaimPolicyRetain, volumeBindingModeIM) + }) + + It("Remove_local_thin_sc_with_existing_thick_lvg", func() { + lvgSpec := []v1alpha1.LocalStorageClassLVG{ + {Name: existingThinLVG1Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-1"}}, + {Name: existingThinLVG2Name, Thin: &v1alpha1.LocalStorageClassThinPool{PoolName: "thin-pool-2"}}, + {Name: existingThickLVG1Name}, + } + + lsc := &v1alpha1.LocalStorageClass{} + err := cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + + err = cl.Delete(ctx, lsc) + Expect(err).NotTo(HaveOccurred()) + + lsc = &v1alpha1.LocalStorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(lsc.Finalizers).To(HaveLen(1)) + Expect(lsc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + Expect(lsc.Spec.LVM.LVMVolumeGroups).To(Equal(lvgSpec)) + + scList := &v1.StorageClassList{} + err = cl.List(ctx, scList) + Expect(err).NotTo(HaveOccurred()) + Expect(scList.Items).To(HaveLen(1)) + + shouldRequeue, err := controller.RunEventReconcile(ctx, cl, log, scList, lsc) + Expect(err).NotTo(HaveOccurred()) + Expect(shouldRequeue).To(BeFalse()) + + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, lsc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + + sc := &v1.StorageClass{} + err = cl.Get(ctx, client.ObjectKey{Name: nameForLocalStorageClass}, sc) + Expect(k8serrors.IsNotFound(err)).To(BeTrue()) + }) + +}) + +func generateLVMVolumeGroup(name string, devices, thinPoolNames []string) *v1alpha1.LvmVolumeGroup { + lvmType := controller.LVMThickType + + if len(thinPoolNames) > 0 { + lvmType = controller.LVMThinType + } + + thinPoolsSpec := make([]v1alpha1.SpecThinPool, 0) + thinPoolsStatus := make([]v1alpha1.StatusThinPool, 0) + for i := 0; i < len(thinPoolNames); i++ { + thinPoolsSpec = append(thinPoolsSpec, v1alpha1.SpecThinPool{ + Name: thinPoolNames[i], + Size: resource.MustParse("10Gi"), + }) + thinPoolsStatus = append(thinPoolsStatus, v1alpha1.StatusThinPool{ + Name: thinPoolNames[i], + ActualSize: resource.MustParse("10Gi"), + UsedSize: resource.MustParse("0Gi"), + }) + } + + return &v1alpha1.LvmVolumeGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: v1alpha1.LvmVolumeGroupSpec{ + ActualVGNameOnTheNode: "vg1", + BlockDeviceNames: devices, + ThinPools: thinPoolsSpec, + Type: lvmType, + }, + Status: v1alpha1.LvmVolumeGroupStatus{ + ThinPools: thinPoolsStatus, + }, + } +} + +func generateLocalStorageClass(lscName, reclaimPolicy, volumeBindingMode, lvmType string, lvgs []v1alpha1.LocalStorageClassLVG) *v1alpha1.LocalStorageClass { + + return &v1alpha1.LocalStorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: lscName, + }, + Spec: v1alpha1.LocalStorageClassSpec{ + ReclaimPolicy: reclaimPolicy, + VolumeBindingMode: volumeBindingMode, + LVM: &v1alpha1.LocalStorageClassLVM{ + Type: lvmType, + LVMVolumeGroups: lvgs, + }, + }, + } + +} + +func performStandartChecksForSC(sc *v1.StorageClass, lvgSpec []v1alpha1.LocalStorageClassLVG, nameForLocalStorageClass, LSCType, LVMType, reclaimPolicy, volumeBindingMode string) { + expectString := "" + for i, lvg := range lvgSpec { + if i != 0 { + expectString += "\n" + } + if lvg.Thin != nil { + expectString += "- name: " + lvg.Name + "\n thin:\n poolName: " + lvg.Thin.PoolName + } else { + expectString += "- name: " + lvg.Name + } + } + expectString += "\n" + + Expect(sc).NotTo(BeNil()) + Expect(sc.Name).To(Equal(nameForLocalStorageClass)) + Expect(sc.Finalizers).To(HaveLen(1)) + Expect(sc.Finalizers).To(ContainElement(controller.LocalStorageClassFinalizerName)) + + Expect(sc.Parameters).To(HaveLen(4)) + Expect(sc.Parameters).To(HaveKeyWithValue(controller.TypeParamKey, LSCType)) + Expect(sc.Parameters).To(HaveKeyWithValue(controller.LVMTypeParamKey, LVMType)) + Expect(sc.Parameters).To(HaveKeyWithValue(controller.LVMVolumeBindingModeParamKey, volumeBindingMode)) + Expect(sc.Parameters).To(HaveKey(controller.LVMVolumeGroupsParamKey)) + Expect(sc.Parameters[controller.LVMVolumeGroupsParamKey]).To(Equal(expectString)) + + Expect(sc.Provisioner).To(Equal(controller.LocalStorageClassProvisioner)) + Expect(string(*sc.ReclaimPolicy)).To(Equal(reclaimPolicy)) + Expect(string(*sc.VolumeBindingMode)).To(Equal(volumeBindingMode)) + Expect(*sc.AllowVolumeExpansion).To(BeTrue()) + +} + +func delFromSlice(slice []v1alpha1.LocalStorageClassLVG, name string) []v1alpha1.LocalStorageClassLVG { + for i, lvg := range slice { + if lvg.Name == name { + // return append(slice[:i], slice[i+1:]...) + return slices.Delete(slice, i, i+1) + } + } + return slice +} diff --git a/images/sds-local-volume-controller/pkg/monitoring/monitoring.go b/images/sds-local-volume-controller/pkg/monitoring/monitoring.go deleted file mode 100644 index a282d9fd..00000000 --- a/images/sds-local-volume-controller/pkg/monitoring/monitoring.go +++ /dev/null @@ -1,150 +0,0 @@ -/* -Copyright 2024 Flant JSC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package monitoring - -import ( - "github.com/prometheus/client_golang/prometheus" - "k8s.io/utils/clock" - "sigs.k8s.io/controller-runtime/pkg/metrics" - "strings" - "time" -) - -const ( - namespace = "sds_node_configurator" -) - -var ( - reconcilesCountTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: namespace, - Name: "reconciles_count_total", - Help: "Total number of times the resources were reconciled.", - }, []string{"node", "controller"}) - - reconcileDuration = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Namespace: namespace, - Name: "reconcile_duration_seconds", - Help: "How long in seconds reconciling of resource takes.", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }, []string{"node", "controller"}) - - utilsCommandsDuration = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Namespace: namespace, - Name: "custom_utils_commands_duration_seconds", - Help: "How long in seconds utils commands execution takes.", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }, []string{"node", "controller", "command"}) - - utilsCommandsExecutionCount = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: namespace, - Name: "utils_commands_execution_count_total", - Help: "Total number of times the util-command was executed.", - }, []string{"node", "controller", "method"}) - - utilsCommandsErrorsCount = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: namespace, - Name: "utils_commands_errors_count_total", - Help: "How many errors occurs during utils-command executions.", - }, []string{"node", "controller", "method"}) - - apiMethodsDuration = prometheus.NewSummaryVec(prometheus.SummaryOpts{ - Namespace: namespace, - Name: "api_commands_duration_seconds", - Help: "How long in seconds kube-api methods execution takes.", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }, []string{"node", "controller", "method"}) - - apiMethodsExecutionCount = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: namespace, - Name: "api_methods_execution_count_total", - Help: "Total number of times the method was executed.", - }, []string{"node", "controller", "method"}) - - apiMethodsErrorsCount = prometheus.NewCounterVec(prometheus.CounterOpts{ - Namespace: namespace, - Name: "api_methods_errors_count_total", - Help: "How many errors occur during api-method executions.", - }, []string{"node", "controller", "method"}) - - noOperationalResourcesCount = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Namespace: namespace, - Name: "no_operational_resources_count_total", - Help: "How many LVMVolumeGroup resources are in Nooperational state.", - }, []string{"resource"}) -) - -func init() { - metrics.Registry.MustRegister(reconcilesCountTotal) - metrics.Registry.MustRegister(reconcileDuration) - metrics.Registry.MustRegister(utilsCommandsDuration) - metrics.Registry.MustRegister(apiMethodsDuration) - metrics.Registry.MustRegister(apiMethodsExecutionCount) - metrics.Registry.MustRegister(apiMethodsErrorsCount) - metrics.Registry.MustRegister(noOperationalResourcesCount) -} - -type Metrics struct { - node string - c clock.Clock -} - -func GetMetrics(nodeName string) Metrics { - return Metrics{ - node: nodeName, - c: clock.RealClock{}, - } -} - -func (m Metrics) GetEstimatedTimeInSeconds(since time.Time) float64 { - return m.c.Since(since).Seconds() -} - -func (m Metrics) ReconcilesCountTotal(controllerName string) prometheus.Counter { - return reconcilesCountTotal.WithLabelValues(m.node, controllerName) -} - -func (m Metrics) ReconcileDuration(controllerName string) prometheus.Observer { - return reconcileDuration.WithLabelValues(m.node, controllerName) -} - -func (m Metrics) UtilsCommandsDuration(controllerName, command string) prometheus.Observer { - return utilsCommandsDuration.WithLabelValues(m.node, controllerName, strings.ToLower(command)) -} - -func (m Metrics) UtilsCommandsExecutionCount(controllerName, command string) prometheus.Counter { - return utilsCommandsExecutionCount.WithLabelValues(m.node, controllerName, strings.ToLower(command)) -} - -func (m Metrics) UtilsCommandsErrorsCount(controllerName, command string) prometheus.Counter { - return utilsCommandsErrorsCount.WithLabelValues(m.node, controllerName, strings.ToLower(command)) -} - -func (m Metrics) ApiMethodsDuration(controllerName, method string) prometheus.Observer { - return apiMethodsDuration.WithLabelValues(m.node, controllerName, strings.ToLower(method)) -} - -func (m Metrics) ApiMethodsExecutionCount(controllerName, method string) prometheus.Counter { - return apiMethodsExecutionCount.WithLabelValues(m.node, controllerName, strings.ToLower(method)) -} - -func (m Metrics) ApiMethodsErrors(controllerName, method string) prometheus.Counter { - return apiMethodsErrorsCount.WithLabelValues(m.node, controllerName, strings.ToLower(method)) -} - -func (m Metrics) NoOperationalResourcesCount(resourceName string) prometheus.Gauge { - return noOperationalResourcesCount.WithLabelValues(strings.ToLower(resourceName)) -} diff --git a/images/sds-local-volume-csi/driver/node.go b/images/sds-local-volume-csi/driver/node.go index 2ec1efc0..5559daa3 100644 --- a/images/sds-local-volume-csi/driver/node.go +++ b/images/sds-local-volume-csi/driver/node.go @@ -232,7 +232,7 @@ func (d *Driver) NodePublishVolume(ctx context.Context, request *csi.NodePublish return nil, status.Errorf(codes.Internal, "[NodePublishVolume] Error checking if device exists: %v", err) } if !exists { - return nil, status.Errorf(codes.NotFound, "[NodePublishVolume] Device %s not found", devPath) + return nil, status.Errorf(codes.NotFound, "[NodePublishVolume] Device %q not found", devPath) } err = d.storeManager.NodePublishVolumeBlock(devPath, target, mountOptions) if err != nil { diff --git a/images/webhooks/src/handlers/lscValidator.go b/images/webhooks/src/handlers/lscValidator.go index 7533d622..145486dd 100644 --- a/images/webhooks/src/handlers/lscValidator.go +++ b/images/webhooks/src/handlers/lscValidator.go @@ -18,13 +18,8 @@ package handlers import ( "context" - "fmt" "webhooks/v1alpha1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/klog/v2" - "github.com/slok/kubewebhook/v2/pkg/model" kwhvalidating "github.com/slok/kubewebhook/v2/pkg/webhook/validating" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -47,29 +42,6 @@ func LSCValidate(ctx context.Context, _ *model.AdmissionReview, obj metav1.Objec } } - if lsc.Spec.IsDefault == true { - config, err := rest.InClusterConfig() - if err != nil { - klog.Fatal(err.Error()) - } - - staticClient, err := kubernetes.NewForConfig(config) - if err != nil { - klog.Fatal(err) - } - - storageClasses, _ := staticClient.StorageV1().StorageClasses().List(context.TODO(), metav1.ListOptions{}) - for _, storageClass := range storageClasses.Items { - for label, value := range storageClass.GetObjectMeta().GetAnnotations() { - if label == "storageclass.kubernetes.io/is-default-class" && value == "true" && storageClass.Name != lsc.Name { - klog.Infof("Default StorageClass already set: %s", storageClass.Name) - return &kwhvalidating.ValidatorResult{Valid: false, Message: fmt.Sprintf("Default StorageClass already set: %s", storageClass.Name)}, - nil - } - } - } - } - if thinExists && lsc.Spec.LVM.Type == "Thick" { return &kwhvalidating.ValidatorResult{Valid: false, Message: "there must be only thick pools with Thick LVM type"}, nil diff --git a/images/webhooks/src/handlers/scValidator.go b/images/webhooks/src/handlers/scValidator.go index 90c6a967..53629cb2 100644 --- a/images/webhooks/src/handlers/scValidator.go +++ b/images/webhooks/src/handlers/scValidator.go @@ -18,7 +18,9 @@ package handlers import ( "context" + "encoding/json" "fmt" + "reflect" "k8s.io/klog/v2" @@ -46,8 +48,24 @@ func SCValidate(ctx context.Context, arReview *model.AdmissionReview, obj metav1 return &kwhvalidating.ValidatorResult{Valid: true}, nil } else { + if arReview.Operation == model.OperationUpdate { + changed, err := isStorageClassChangedExceptAnnotations(arReview.OldObjectRaw, arReview.NewObjectRaw) + if err != nil { + return nil, err + } + + if !changed { + klog.Infof("User %s is allowed to change annotations for storage classes with provisioner %s", arReview.UserInfo.Username, localCSIProvisioner) + return &kwhvalidating.ValidatorResult{Valid: true}, + nil + } + } + klog.Infof("User %s is not allowed to manage storage classes with provisioner %s", arReview.UserInfo.Username, localCSIProvisioner) - return &kwhvalidating.ValidatorResult{Valid: false, Message: fmt.Sprintf("Manual operations with StorageClass with provisioner %s are not allowed. Please use LocalStorageClass instead.", localCSIProvisioner)}, + return &kwhvalidating.ValidatorResult{ + Valid: false, + Message: fmt.Sprintf("Direct modifications to the StorageClass (other than annotations) with the provisioner %s are not allowed. Please use LocalStorageClass for such operations.", localCSIProvisioner), + }, nil } } else { @@ -56,3 +74,71 @@ func SCValidate(ctx context.Context, arReview *model.AdmissionReview, obj metav1 } } + +func isStorageClassChangedExceptAnnotations(oldObjectRaw, newObjectRaw []byte) (bool, error) { + var oldSC, newSC storagev1.StorageClass + + if err := json.Unmarshal(oldObjectRaw, &oldSC); err != nil { + err := fmt.Errorf("failed to unmarshal old object: %v", err) + return false, err + } + + if err := json.Unmarshal(newObjectRaw, &newSC); err != nil { + err := fmt.Errorf("failed to unmarshal new object: %v", err) + return false, err + } + + klog.Info("=====================================") + klog.Infof("Comparing old object: %+v", oldSC) + klog.Info("=====================================") + klog.Infof("Comparing new object: %+v", newSC) + klog.Info("=====================================") + + if oldSC.Provisioner != newSC.Provisioner { + klog.Infof("Provisioner changed from %s to %s", oldSC.Provisioner, newSC.Provisioner) + return true, nil + } + + if *oldSC.VolumeBindingMode != *newSC.VolumeBindingMode { + klog.Infof("VolumeBindingMode changed from %s to %s", *oldSC.VolumeBindingMode, *newSC.VolumeBindingMode) + return true, nil + } + + if *oldSC.ReclaimPolicy != *newSC.ReclaimPolicy { + klog.Infof("ReclaimPolicy changed from %s to %s", *oldSC.ReclaimPolicy, *newSC.ReclaimPolicy) + return true, nil + } + + if !reflect.DeepEqual(oldSC.Parameters, newSC.Parameters) { + klog.Infof("Parameters changed from %v to %v", oldSC.Parameters, newSC.Parameters) + return true, nil + } + + if *oldSC.AllowVolumeExpansion != *newSC.AllowVolumeExpansion { + klog.Infof("AllowVolumeExpansion changed from %v to %v", *oldSC.AllowVolumeExpansion, *newSC.AllowVolumeExpansion) + return true, nil + } + + if !reflect.DeepEqual(oldSC.MountOptions, newSC.MountOptions) { + klog.Infof("MountOptions changed from %v to %v", oldSC.MountOptions, newSC.MountOptions) + return true, nil + } + + if !reflect.DeepEqual(oldSC.AllowedTopologies, newSC.AllowedTopologies) { + klog.Infof("AllowedTopologies changed from %v to %v", oldSC.AllowedTopologies, newSC.AllowedTopologies) + return true, nil + } + + newSC.ObjectMeta.Annotations = nil + oldSC.ObjectMeta.Annotations = nil + + newSC.ObjectMeta.ManagedFields = nil + oldSC.ObjectMeta.ManagedFields = nil + + if !reflect.DeepEqual(oldSC.ObjectMeta, newSC.ObjectMeta) { + klog.Infof("ObjectMeta changed from %+v to %+v", oldSC.ObjectMeta, newSC.ObjectMeta) + return true, nil + } + + return false, nil +} diff --git a/images/webhooks/src/v1alpha1/local_storage_class.go b/images/webhooks/src/v1alpha1/local_storage_class.go index 18bbee34..015ffe46 100644 --- a/images/webhooks/src/v1alpha1/local_storage_class.go +++ b/images/webhooks/src/v1alpha1/local_storage_class.go @@ -33,7 +33,6 @@ type LocalStorageClassList struct { } type LocalStorageClassSpec struct { - IsDefault bool `json:"isDefault"` ReclaimPolicy string `json:"reclaimPolicy"` VolumeBindingMode string `json:"volumeBindingMode"` LVM *LocalStorageClassLVM `json:"lvm,omitempty"`