diff --git a/bundle/manifests/lvms-operator.clusterserviceversion.yaml b/bundle/manifests/lvms-operator.clusterserviceversion.yaml index 8c1e46319..8a1a0c3d4 100644 --- a/bundle/manifests/lvms-operator.clusterserviceversion.yaml +++ b/bundle/manifests/lvms-operator.clusterserviceversion.yaml @@ -853,6 +853,18 @@ spec: - create - patch - update + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch serviceAccountName: vg-manager strategy: deployment installModes: diff --git a/cmd/vgmanager/vgmanager.go b/cmd/vgmanager/vgmanager.go index 1bc451575..f7a7971e1 100644 --- a/cmd/vgmanager/vgmanager.go +++ b/cmd/vgmanager/vgmanager.go @@ -21,6 +21,7 @@ import ( "os" "github.com/go-logr/logr" + "github.com/openshift/lvm-operator/internal/cluster" "github.com/openshift/lvm-operator/internal/controllers/vgmanager" "github.com/openshift/lvm-operator/internal/controllers/vgmanager/dmsetup" "github.com/openshift/lvm-operator/internal/controllers/vgmanager/filter" @@ -84,14 +85,18 @@ func NewCmd(opts *Options) *cobra.Command { func run(cmd *cobra.Command, _ []string, opts *Options) error { lvm := lvm.NewDefaultHostLVM() nodeName := os.Getenv("NODE_NAME") - namespace := os.Getenv("POD_NAMESPACE") + + operatorNamespace, err := cluster.GetOperatorNamespace() + if err != nil { + return fmt.Errorf("unable to get operatorNamespace: %w", err) + } setupClient, err := client.New(ctrl.GetConfigOrDie(), client.Options{Scheme: opts.Scheme}) if err != nil { return fmt.Errorf("unable to initialize setup client for pre-manager startup checks: %w", err) } - if err := tagging.AddTagToVGs(cmd.Context(), setupClient, lvm, nodeName, namespace); err != nil { + if err := tagging.AddTagToVGs(cmd.Context(), setupClient, lvm, nodeName, operatorNamespace); err != nil { opts.SetupLog.Error(err, "failed to tag existing volume groups") } @@ -109,7 +114,7 @@ func run(cmd *cobra.Command, _ []string, opts *Options) error { LeaderElection: false, Cache: cache.Options{ DefaultNamespaces: map[string]cache.Config{ - os.Getenv("POD_NAMESPACE"): {}, + operatorNamespace: {}, }, }, }) @@ -120,14 +125,14 @@ func run(cmd *cobra.Command, _ []string, opts *Options) error { if err = (&vgmanager.Reconciler{ Client: mgr.GetClient(), EventRecorder: mgr.GetEventRecorderFor(vgmanager.ControllerName), - LVMD: lvmd.DefaultConfigurator(), + LVMD: lvmd.NewFileConfigurator(mgr.GetClient(), operatorNamespace), Scheme: mgr.GetScheme(), LSBLK: lsblk.NewDefaultHostLSBLK(), Wipefs: wipefs.NewDefaultHostWipefs(), Dmsetup: dmsetup.NewDefaultHostDmsetup(), LVM: lvm, NodeName: nodeName, - Namespace: namespace, + Namespace: operatorNamespace, Filters: filter.DefaultFilters, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create controller VGManager: %w", err) diff --git a/config/rbac/vg_manager_role.yaml b/config/rbac/vg_manager_role.yaml index 8cc42ecdf..4e8cde82f 100644 --- a/config/rbac/vg_manager_role.yaml +++ b/config/rbac/vg_manager_role.yaml @@ -66,3 +66,15 @@ rules: - create - patch - update +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/internal/controllers/constants/constants.go b/internal/controllers/constants/constants.go index 8a759d3e5..91058078e 100644 --- a/internal/controllers/constants/constants.go +++ b/internal/controllers/constants/constants.go @@ -61,9 +61,6 @@ const ( LivenessProbeMemRequest = "15Mi" LivenessProbeCPURequest = "1m" - FileCheckerMemRequest = "10Mi" - FileCheckerCPURequest = "1m" - // topoLVM Node TopolvmNodeServiceAccount = "topolvm-node" TopolvmNodeDaemonsetName = "topolvm-node" @@ -74,6 +71,10 @@ const ( DefaultCSISocket = "/run/topolvm/csi-topolvm.sock" DeviceClassKey = "topolvm.io/device-class" + LVMDConfigMapName = "lvmd-config" + LVMDDefaultConfigDir = "/etc/topolvm" + LVMDDefaultFileConfigPath = "/etc/topolvm/lvmd.yaml" + // name of the lvm-operator container LVMOperatorContainerName = "manager" diff --git a/internal/controllers/lvmcluster/resource/topolvm_node.go b/internal/controllers/lvmcluster/resource/topolvm_node.go index 2389086e6..43bb9fde6 100644 --- a/internal/controllers/lvmcluster/resource/topolvm_node.go +++ b/internal/controllers/lvmcluster/resource/topolvm_node.go @@ -25,7 +25,6 @@ import ( lvmv1alpha1 "github.com/openshift/lvm-operator/api/v1alpha1" "github.com/openshift/lvm-operator/internal/controllers/constants" "github.com/openshift/lvm-operator/internal/controllers/lvmcluster/selector" - "github.com/openshift/lvm-operator/internal/controllers/vgmanager/lvmd" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -59,7 +58,6 @@ func (n topolvmNode) EnsureCreated(r Reconciler, ctx context.Context, lvmCluster // get desired daemonSet spec dsTemplate := getNodeDaemonSet(lvmCluster, r.GetNamespace(), - r.GetImageName(), r.GetLogPassthroughOptions().TopoLVMNode.AsArgs(), r.GetLogPassthroughOptions().CSISideCar.AsArgs(), ) @@ -83,14 +81,11 @@ func (n topolvmNode) EnsureCreated(r Reconciler, ctx context.Context, lvmCluster return nil } // if update, update only mutable fields - // For topolvm Node, we have containers, initcontainers, node selector and toleration terms + // For topolvm Node, we have containers, node selector and toleration terms // containers ds.Spec.Template.Spec.Containers = dsTemplate.Spec.Template.Spec.Containers - // initcontainers - ds.Spec.Template.Spec.InitContainers = dsTemplate.Spec.Template.Spec.InitContainers - // tolerations ds.Spec.Template.Spec.Tolerations = dsTemplate.Spec.Template.Spec.Tolerations @@ -121,7 +116,7 @@ func (n topolvmNode) EnsureDeleted(_ Reconciler, _ context.Context, _ *lvmv1alph return nil } -func getNodeDaemonSet(lvmCluster *lvmv1alpha1.LVMCluster, namespace string, initImage string, args, csiArgs []string) *appsv1.DaemonSet { +func getNodeDaemonSet(lvmCluster *lvmv1alpha1.LVMCluster, namespace string, args, csiArgs []string) *appsv1.DaemonSet { hostPathDirectory := corev1.HostPathDirectory hostPathDirectoryOrCreateType := corev1.HostPathDirectoryOrCreate @@ -149,9 +144,12 @@ func getNodeDaemonSet(lvmCluster *lvmv1alpha1.LVMCluster, namespace string, init Type: &hostPathDirectoryOrCreateType}}}, {Name: "lvmd-config-dir", VolumeSource: corev1.VolumeSource{ - HostPath: &corev1.HostPathVolumeSource{ - Path: filepath.Dir(lvmd.DefaultFileConfigPath), - Type: &hostPathDirectory}}}, + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: constants.LVMDConfigMapName, + }, + }, + }}, {Name: "metrics-cert", VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ @@ -161,7 +159,6 @@ func getNodeDaemonSet(lvmCluster *lvmv1alpha1.LVMCluster, namespace string, init }}, } - initContainers := []corev1.Container{*getNodeInitContainer(initImage)} containers := []corev1.Container{ *getNodeContainer(args), *getRBACProxyContainer(), @@ -204,7 +201,6 @@ func getNodeDaemonSet(lvmCluster *lvmv1alpha1.LVMCluster, namespace string, init }, Spec: corev1.PodSpec{ ServiceAccountName: constants.TopolvmNodeServiceAccount, - InitContainers: initContainers, Containers: containers, Volumes: volumes, HostPID: true, @@ -220,38 +216,11 @@ func getNodeDaemonSet(lvmCluster *lvmv1alpha1.LVMCluster, namespace string, init return nodeDaemonSet } -func getNodeInitContainer(initImage string) *corev1.Container { - command := []string{ - "/usr/bin/bash", - "-c", - fmt.Sprintf("until [ -f %s ]; do echo waiting for lvmd config file; sleep 5; done", lvmd.DefaultFileConfigPath), - } - - volumeMounts := []corev1.VolumeMount{ - {Name: "lvmd-config-dir", MountPath: filepath.Dir(lvmd.DefaultFileConfigPath)}, - } - - fileChecker := &corev1.Container{ - Name: "file-checker", - Image: initImage, - Command: command, - VolumeMounts: volumeMounts, - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse(constants.FileCheckerCPURequest), - corev1.ResourceMemory: resource.MustParse(constants.FileCheckerMemRequest), - }, - }, - } - - return fileChecker -} - func getNodeContainer(args []string) *corev1.Container { command := []string{ "/topolvm-node", "--embed-lvmd", - fmt.Sprintf("--config=%s", lvmd.DefaultFileConfigPath), + fmt.Sprintf("--config=%s", constants.LVMDDefaultFileConfigPath), } command = append(command, args...) @@ -267,7 +236,7 @@ func getNodeContainer(args []string) *corev1.Container { volumeMounts := []corev1.VolumeMount{ {Name: "node-plugin-dir", MountPath: filepath.Dir(constants.DefaultCSISocket)}, - {Name: "lvmd-config-dir", MountPath: filepath.Dir(lvmd.DefaultFileConfigPath)}, + {Name: "lvmd-config-dir", MountPath: constants.LVMDDefaultConfigDir}, {Name: "pod-volumes-dir", MountPath: fmt.Sprintf("%spods", getAbsoluteKubeletPath(constants.CSIKubeletRootDir)), MountPropagation: &mountPropagationMode}, diff --git a/internal/controllers/vgmanager/controller.go b/internal/controllers/vgmanager/controller.go index ed878f9d6..a7d668cfb 100644 --- a/internal/controllers/vgmanager/controller.go +++ b/internal/controllers/vgmanager/controller.go @@ -31,6 +31,7 @@ import ( "github.com/openshift/lvm-operator/internal/controllers/vgmanager/lvm" "github.com/openshift/lvm-operator/internal/controllers/vgmanager/lvmd" "github.com/openshift/lvm-operator/internal/controllers/vgmanager/wipefs" + corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -43,8 +44,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) const ( @@ -79,9 +82,38 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&lvmv1alpha1.LVMVolumeGroup{}). Owns(&lvmv1alpha1.LVMVolumeGroupNodeStatus{}, builder.MatchEveryOwner, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(r.getObjsInNamespaceForReconcile)). Complete(r) } +// getObjsInNamespaceForReconcile reconciles the object anytime the given object is in the same namespace +// as the available LVMVolumeGroups. +func (r *Reconciler) getObjsInNamespaceForReconcile(ctx context.Context, obj client.Object) []reconcile.Request { + foundLVMVolumeGroupList := &lvmv1alpha1.LVMVolumeGroupList{} + listOps := &client.ListOptions{ + Namespace: obj.GetNamespace(), + } + + if err := r.List(ctx, foundLVMVolumeGroupList, listOps); err != nil { + log.FromContext(ctx).Error(err, "getObjsInNamespaceForReconcile: Failed to get LVMVolumeGroup objs") + return []reconcile.Request{} + } + if len(foundLVMVolumeGroupList.Items) < 1 { + return []reconcile.Request{} + } + + var requests []reconcile.Request + for _, lvmVG := range foundLVMVolumeGroupList.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: lvmVG.GetName(), + Namespace: lvmVG.GetNamespace(), + }, + }) + } + return requests +} + type Reconciler struct { client.Client Scheme *runtime.Scheme @@ -288,7 +320,7 @@ func (r *Reconciler) applyLVMDConfig(ctx context.Context, volumeGroup *lvmv1alph logger := log.FromContext(ctx).WithValues("VGName", volumeGroup.Name) // Read the lvmd config file - lvmdConfig, err := r.LVMD.Load() + lvmdConfig, err := r.LVMD.Load(ctx) if err != nil { err = fmt.Errorf("failed to read the lvmd config file: %w", err) if _, err := r.setVolumeGroupFailedStatus(ctx, volumeGroup, nil, err); err != nil { @@ -354,7 +386,7 @@ func (r *Reconciler) updateLVMDConfigAfterReconcile( r.NormalEvent(ctx, volumeGroup, EventReasonLVMDConfigMissing, msg) } - if err := r.LVMD.Save(newCFG); err != nil { + if err := r.LVMD.Save(ctx, newCFG); err != nil { return fmt.Errorf("failed to update lvmd config file to update volume group %s: %w", volumeGroup.GetName(), err) } msg := "updated lvmd config with new deviceClasses" @@ -369,7 +401,7 @@ func (r *Reconciler) processDelete(ctx context.Context, volumeGroup *lvmv1alpha1 logger.Info("deleting") // Read the lvmd config file - lvmdConfig, err := r.LVMD.Load() + lvmdConfig, err := r.LVMD.Load(ctx) if err != nil { // Failed to read lvmdconfig file. Reconcile again return fmt.Errorf("failed to read the lvmd config file: %w", err) @@ -437,14 +469,14 @@ func (r *Reconciler) processDelete(ctx context.Context, volumeGroup *lvmv1alpha1 // if there was no config file in the first place, nothing has to be removed. if lvmdConfig != nil { if len(lvmdConfig.DeviceClasses) > 0 { - if err = r.LVMD.Save(lvmdConfig); err != nil { + if err = r.LVMD.Save(ctx, lvmdConfig); err != nil { return fmt.Errorf("failed to update lvmd.conf file for volume group %s: %w", volumeGroup.GetName(), err) } msg := "updated lvmd config after deviceClass was removed" logger.Info(msg) r.NormalEvent(ctx, volumeGroup, EventReasonLVMDConfigUpdated, msg) } else { - if err = r.LVMD.Delete(); err != nil { + if err = r.LVMD.Delete(ctx); err != nil { return fmt.Errorf("failed to delete lvmd.conf file for volume group %s: %w", volumeGroup.GetName(), err) } msg := "removed lvmd config after last deviceClass was removed" diff --git a/internal/controllers/vgmanager/controller_test.go b/internal/controllers/vgmanager/controller_test.go index ea3ee0828..e924e4b89 100644 --- a/internal/controllers/vgmanager/controller_test.go +++ b/internal/controllers/vgmanager/controller_test.go @@ -24,11 +24,15 @@ import ( "github.com/openshift/lvm-operator/internal/controllers/vgmanager/lvmd" lvmdmocks "github.com/openshift/lvm-operator/internal/controllers/vgmanager/lvmd/mocks" wipefsmocks "github.com/openshift/lvm-operator/internal/controllers/vgmanager/wipefs/mocks" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" topolvmv1 "github.com/topolvm/topolvm/api/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -107,7 +111,6 @@ func setupInstances() testInstances { mockLSBLK := lsblkmocks.NewMockLSBLK(t) mockLVM := lvmmocks.NewMockLVM(t) mockWipefs := wipefsmocks.NewMockWipefs(t) - testLVMD := lvmd.NewFileConfigurator(filepath.Join(t.TempDir(), "lvmd.yaml")) hostname := "test-host.vgmanager.test.io" hostnameLabelKey := "kubernetes.io/hostname" @@ -123,6 +126,8 @@ func setupInstances() testInstances { fakeRecorder := record.NewFakeRecorder(100) fakeRecorder.IncludeObject = true + testLVMD := lvmd.NewFileConfigurator(fakeClient, namespace.GetName()) + return testInstances{ LVM: mockLVM, LSBLK: mockLSBLK, @@ -302,7 +307,7 @@ func testMockedBlockDeviceOnHost(ctx context.Context) { By("verifying the lvmd config generation", func() { checkDistributedEvent(corev1.EventTypeNormal, "lvmd config file doesn't exist, will attempt to create a fresh config") checkDistributedEvent(corev1.EventTypeNormal, "updated lvmd config with new deviceClasses") - lvmdConfig, err := instances.LVMD.Load() + lvmdConfig, err := instances.LVMD.Load(ctx) Expect(err).ToNot(HaveOccurred()) Expect(lvmdConfig).ToNot(BeNil()) Expect(lvmdConfig.DeviceClasses).ToNot(BeNil()) @@ -392,7 +397,7 @@ func testMockedBlockDeviceOnHost(ctx context.Context) { _, err := instances.Reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(vg)}) Expect(err).ToNot(HaveOccurred()) Expect(instances.client.Get(ctx, client.ObjectKeyFromObject(vg), vg)).ToNot(Succeed()) - lvmdConfig, err := instances.LVMD.Load() + lvmdConfig, err := instances.LVMD.Load(ctx) Expect(err).ToNot(HaveOccurred()) Expect(lvmdConfig).To(BeNil()) }) @@ -547,13 +552,13 @@ func testLVMD(ctx context.Context) { mockLVM := lvmmocks.NewMockLVM(GinkgoT()) r.LVM = mockLVM - mockLVMD.EXPECT().Load().Once().Return(nil, fmt.Errorf("mock load failure")) + mockLVMD.EXPECT().Load(ctx).Once().Return(nil, fmt.Errorf("mock load failure")) mockLVM.EXPECT().ListVGs().Once().Return(nil, fmt.Errorf("mock list failure")) err := r.applyLVMDConfig(ctx, vg, devices) Expect(err).To(HaveOccurred(), "should error if lvmd config cannot be loaded and/or status cannot be set") - mockLVMD.EXPECT().Load().Once().Return(&lvmd.Config{}, nil) - mockLVMD.EXPECT().Save(mock.Anything).Once().Return(fmt.Errorf("mock save failure")) + mockLVMD.EXPECT().Load(ctx).Once().Return(&lvmd.Config{}, nil) + mockLVMD.EXPECT().Save(ctx, mock.Anything).Once().Return(fmt.Errorf("mock save failure")) mockLVM.EXPECT().ListVGs().Once().Return(nil, fmt.Errorf("mock list failure")) err = r.applyLVMDConfig(ctx, vg, devices) Expect(err).To(HaveOccurred(), "should error if lvmd config cannot be saved and/or status cannot be set") @@ -823,3 +828,52 @@ func testReconcileFailure(ctx context.Context) { Expect(err).To(HaveOccurred()) }) } + +func TestGetObjsInNamespaceForReconcile(t *testing.T) { + tests := []struct { + name string + objs []client.Object + list error + expect []reconcile.Request + }{ + { + name: "test lvmvolumegroup not fetch error", + list: assert.AnError, + }, + { + name: "test lvmvolumegroup found in a different namespace", + objs: []client.Object{ + &lvmv1alpha1.LVMVolumeGroup{ObjectMeta: metav1.ObjectMeta{Name: "test-vg", Namespace: "not-test"}}, + }, + }, + { + name: "test lvmvolumegroup found in the same namespace", + objs: []client.Object{ + &lvmv1alpha1.LVMVolumeGroup{ObjectMeta: metav1.ObjectMeta{Name: "test-vg", Namespace: "test"}}, + }, + expect: []reconcile.Request{{NamespacedName: types.NamespacedName{Name: "test-vg", Namespace: "test"}}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + newScheme := runtime.NewScheme() + assert.NoError(t, lvmv1alpha1.AddToScheme(newScheme)) + assert.NoError(t, corev1.AddToScheme(newScheme)) + clnt := fake.NewClientBuilder().WithObjects(tt.objs...). + WithScheme(newScheme).WithInterceptorFuncs(interceptor.Funcs{ + List: func(ctx context.Context, client client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + if tt.list != nil { + return tt.list + } + return client.List(ctx, list, opts...) + }, + }).Build() + + r := &Reconciler{Client: clnt} + requests := r.getObjsInNamespaceForReconcile(context.Background(), + &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-vg", Namespace: "test"}}) + assert.ElementsMatch(t, tt.expect, requests) + }) + } +} diff --git a/internal/controllers/vgmanager/lvmd/lvmd.go b/internal/controllers/vgmanager/lvmd/lvmd.go index 625662d46..0ca096d46 100644 --- a/internal/controllers/vgmanager/lvmd/lvmd.go +++ b/internal/controllers/vgmanager/lvmd/lvmd.go @@ -1,11 +1,20 @@ package lvmd import ( + "context" "fmt" "os" + "github.com/openshift/lvm-operator/internal/controllers/constants" "github.com/topolvm/topolvm/lvmd" lvmdCMD "github.com/topolvm/topolvm/pkg/lvmd/cmd" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/yaml" ) @@ -16,57 +25,89 @@ type ThinPoolConfig = lvmd.ThinPoolConfig var TypeThin = lvmd.TypeThin -const DefaultFileConfigPath = "/etc/topolvm/lvmd.yaml" - -func DefaultConfigurator() FileConfig { - return NewFileConfigurator(DefaultFileConfigPath) -} - -func NewFileConfigurator(path string) FileConfig { - return FileConfig{path: path} +func NewFileConfigurator(client client.Client, namespace string) FileConfig { + return FileConfig{Client: client, Namespace: namespace} } type Configurator interface { - Load() (*Config, error) - Save(config *Config) error - Delete() error + Load(ctx context.Context) (*Config, error) + Save(ctx context.Context, config *Config) error + Delete(ctx context.Context) error } type FileConfig struct { - path string + client.Client + Namespace string } -func (c FileConfig) Load() (*Config, error) { - cfgBytes, err := os.ReadFile(c.path) - if os.IsNotExist(err) { +func (c FileConfig) Load(ctx context.Context) (*Config, error) { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.LVMDConfigMapName, + Namespace: c.Namespace, + }, + } + err := c.Client.Get(ctx, client.ObjectKeyFromObject(cm), cm) + if k8serrors.IsNotFound(err) { // If the file does not exist, return nil for both return nil, nil } else if err != nil { - return nil, fmt.Errorf("failed to load config file %s: %w", c.path, err) - } else { - config := &Config{} - if err = yaml.Unmarshal(cfgBytes, config); err != nil { - return nil, fmt.Errorf("failed to unmarshal config file %s: %w", c.path, err) - } - return config, nil + return nil, fmt.Errorf("failed to get ConfigMap %s: %w", cm.Name, err) } + + config := &Config{} + if err = yaml.Unmarshal([]byte(cm.Data["lvmd.yaml"]), config); err != nil { + return nil, fmt.Errorf("failed to unmarshal config file: %w", err) + } + return config, nil } -func (c FileConfig) Save(config *Config) error { - out, err := yaml.Marshal(config) +func (c FileConfig) Save(ctx context.Context, config *Config) error { + logger := log.FromContext(ctx) + // TODO: removing the old config file is added for seamless upgrades from 4.14 to 4.15, and should be deleted in 4.16 + // remove the old config file if it still exists + _, err := os.ReadFile(constants.LVMDDefaultFileConfigPath) if err == nil { - err = os.WriteFile(c.path, out, 0600) + if err = os.Remove(constants.LVMDDefaultFileConfigPath); err != nil { + logger.Info("failed to remove the old lvmd config file", "filePath", constants.LVMDDefaultFileConfigPath, "error", err) + } } + out, err := yaml.Marshal(config) if err != nil { - return fmt.Errorf("failed to save config file %s: %w", c.path, err) + return fmt.Errorf("failed to marshal config file: %w", err) } + + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.LVMDConfigMapName, + Namespace: c.Namespace, + }, + } + _, err = ctrl.CreateOrUpdate(ctx, c.Client, cm, func() error { + cm.Data = map[string]string{"lvmd.yaml": string(out)} + return nil + }) + if err != nil { + return fmt.Errorf("failed to apply ConfigMap %s: %w", cm.GetName(), err) + } + return nil } -func (c FileConfig) Delete() error { - err := os.Remove(c.path) - if err != nil { - return fmt.Errorf("failed to delete config file %s: %w", c.path, err) +func (c FileConfig) Delete(ctx context.Context) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.LVMDConfigMapName, + Namespace: c.Namespace, + }, + } + if err := c.Client.Delete(ctx, cm); err != nil { + if k8serrors.IsNotFound(err) { + // If the file does not exist, return nil + return nil + } + return fmt.Errorf("failed to delete ConfigMap: %w", err) } - return err + + return nil } diff --git a/internal/controllers/vgmanager/lvmd/mocks/mock_configurator.go b/internal/controllers/vgmanager/lvmd/mocks/mock_configurator.go index 1905efe98..27d162b32 100644 --- a/internal/controllers/vgmanager/lvmd/mocks/mock_configurator.go +++ b/internal/controllers/vgmanager/lvmd/mocks/mock_configurator.go @@ -3,6 +3,8 @@ package lvmd import ( + context "context" + cmd "github.com/topolvm/topolvm/pkg/lvmd/cmd" mock "github.com/stretchr/testify/mock" @@ -21,13 +23,13 @@ func (_m *MockConfigurator) EXPECT() *MockConfigurator_Expecter { return &MockConfigurator_Expecter{mock: &_m.Mock} } -// Delete provides a mock function with given fields: -func (_m *MockConfigurator) Delete() error { - ret := _m.Called() +// Delete provides a mock function with given fields: ctx +func (_m *MockConfigurator) Delete(ctx context.Context) error { + ret := _m.Called(ctx) var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) } else { r0 = ret.Error(0) } @@ -41,13 +43,14 @@ type MockConfigurator_Delete_Call struct { } // Delete is a helper method to define mock.On call -func (_e *MockConfigurator_Expecter) Delete() *MockConfigurator_Delete_Call { - return &MockConfigurator_Delete_Call{Call: _e.mock.On("Delete")} +// - ctx context.Context +func (_e *MockConfigurator_Expecter) Delete(ctx interface{}) *MockConfigurator_Delete_Call { + return &MockConfigurator_Delete_Call{Call: _e.mock.On("Delete", ctx)} } -func (_c *MockConfigurator_Delete_Call) Run(run func()) *MockConfigurator_Delete_Call { +func (_c *MockConfigurator_Delete_Call) Run(run func(ctx context.Context)) *MockConfigurator_Delete_Call { _c.Call.Run(func(args mock.Arguments) { - run() + run(args[0].(context.Context)) }) return _c } @@ -57,30 +60,30 @@ func (_c *MockConfigurator_Delete_Call) Return(_a0 error) *MockConfigurator_Dele return _c } -func (_c *MockConfigurator_Delete_Call) RunAndReturn(run func() error) *MockConfigurator_Delete_Call { +func (_c *MockConfigurator_Delete_Call) RunAndReturn(run func(context.Context) error) *MockConfigurator_Delete_Call { _c.Call.Return(run) return _c } -// Load provides a mock function with given fields: -func (_m *MockConfigurator) Load() (*cmd.Config, error) { - ret := _m.Called() +// Load provides a mock function with given fields: ctx +func (_m *MockConfigurator) Load(ctx context.Context) (*cmd.Config, error) { + ret := _m.Called(ctx) var r0 *cmd.Config var r1 error - if rf, ok := ret.Get(0).(func() (*cmd.Config, error)); ok { - return rf() + if rf, ok := ret.Get(0).(func(context.Context) (*cmd.Config, error)); ok { + return rf(ctx) } - if rf, ok := ret.Get(0).(func() *cmd.Config); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) *cmd.Config); ok { + r0 = rf(ctx) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*cmd.Config) } } - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) } else { r1 = ret.Error(1) } @@ -94,13 +97,14 @@ type MockConfigurator_Load_Call struct { } // Load is a helper method to define mock.On call -func (_e *MockConfigurator_Expecter) Load() *MockConfigurator_Load_Call { - return &MockConfigurator_Load_Call{Call: _e.mock.On("Load")} +// - ctx context.Context +func (_e *MockConfigurator_Expecter) Load(ctx interface{}) *MockConfigurator_Load_Call { + return &MockConfigurator_Load_Call{Call: _e.mock.On("Load", ctx)} } -func (_c *MockConfigurator_Load_Call) Run(run func()) *MockConfigurator_Load_Call { +func (_c *MockConfigurator_Load_Call) Run(run func(ctx context.Context)) *MockConfigurator_Load_Call { _c.Call.Run(func(args mock.Arguments) { - run() + run(args[0].(context.Context)) }) return _c } @@ -110,18 +114,18 @@ func (_c *MockConfigurator_Load_Call) Return(_a0 *cmd.Config, _a1 error) *MockCo return _c } -func (_c *MockConfigurator_Load_Call) RunAndReturn(run func() (*cmd.Config, error)) *MockConfigurator_Load_Call { +func (_c *MockConfigurator_Load_Call) RunAndReturn(run func(context.Context) (*cmd.Config, error)) *MockConfigurator_Load_Call { _c.Call.Return(run) return _c } -// Save provides a mock function with given fields: config -func (_m *MockConfigurator) Save(config *cmd.Config) error { - ret := _m.Called(config) +// Save provides a mock function with given fields: ctx, config +func (_m *MockConfigurator) Save(ctx context.Context, config *cmd.Config) error { + ret := _m.Called(ctx, config) var r0 error - if rf, ok := ret.Get(0).(func(*cmd.Config) error); ok { - r0 = rf(config) + if rf, ok := ret.Get(0).(func(context.Context, *cmd.Config) error); ok { + r0 = rf(ctx, config) } else { r0 = ret.Error(0) } @@ -135,14 +139,15 @@ type MockConfigurator_Save_Call struct { } // Save is a helper method to define mock.On call +// - ctx context.Context // - config *cmd.Config -func (_e *MockConfigurator_Expecter) Save(config interface{}) *MockConfigurator_Save_Call { - return &MockConfigurator_Save_Call{Call: _e.mock.On("Save", config)} +func (_e *MockConfigurator_Expecter) Save(ctx interface{}, config interface{}) *MockConfigurator_Save_Call { + return &MockConfigurator_Save_Call{Call: _e.mock.On("Save", ctx, config)} } -func (_c *MockConfigurator_Save_Call) Run(run func(config *cmd.Config)) *MockConfigurator_Save_Call { +func (_c *MockConfigurator_Save_Call) Run(run func(ctx context.Context, config *cmd.Config)) *MockConfigurator_Save_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(*cmd.Config)) + run(args[0].(context.Context), args[1].(*cmd.Config)) }) return _c } @@ -152,7 +157,7 @@ func (_c *MockConfigurator_Save_Call) Return(_a0 error) *MockConfigurator_Save_C return _c } -func (_c *MockConfigurator_Save_Call) RunAndReturn(run func(*cmd.Config) error) *MockConfigurator_Save_Call { +func (_c *MockConfigurator_Save_Call) RunAndReturn(run func(context.Context, *cmd.Config) error) *MockConfigurator_Save_Call { _c.Call.Return(run) return _c }