diff --git a/bundle/manifests/lvms-operator.clusterserviceversion.yaml b/bundle/manifests/lvms-operator.clusterserviceversion.yaml index ab5635c14..7121d1e92 100644 --- a/bundle/manifests/lvms-operator.clusterserviceversion.yaml +++ b/bundle/manifests/lvms-operator.clusterserviceversion.yaml @@ -129,8 +129,12 @@ spec: resources: - deployments verbs: + - create + - delete - get - list + - patch + - update - watch - apiGroups: - apps diff --git a/catalog/lvms-operator/operator.yaml b/catalog/lvms-operator/operator.yaml index 5255bb6aa..6b574a062 100644 --- a/catalog/lvms-operator/operator.yaml +++ b/catalog/lvms-operator/operator.yaml @@ -86,13 +86,25 @@ properties: } } operatorframework.io/suggested-namespace: openshift-storage + operatorframework.io/suggested-namespace-template: |- + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": "openshift-storage", + "annotations": { + "workload.openshift.io/allowed": "management" + } + } + } operators.openshift.io/infrastructure-features: '["csi", "disconnected"]' - operators.operatorframework.io/builder: operator-sdk-v1.25.3 + operators.openshift.io/valid-subscription: '["OpenShift Container Platform", + "OpenShift Platform Plus"]' + operators.operatorframework.io/builder: operator-sdk-v1.33.0 operators.operatorframework.io/internal-objects: '["logicalvolumes.topolvm.io", "lvmvolumegroups.lvm.topolvm.io", "lvmvolumegroupnodestatuses.lvm.topolvm.io"]' operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 repository: https://github.com/openshift/lvm-operator - target.workload.openshift.io/management: '{"effect": "PreferredDuringScheduling"}' apiServiceDefinitions: {} crdDescriptions: owned: diff --git a/cmd/operator/operator.go b/cmd/operator/operator.go index 54100d884..9450d606a 100644 --- a/cmd/operator/operator.go +++ b/cmd/operator/operator.go @@ -33,6 +33,7 @@ import ( "github.com/openshift/lvm-operator/internal/controllers/persistent-volume" "github.com/openshift/lvm-operator/internal/controllers/persistent-volume-claim" internalCSI "github.com/openshift/lvm-operator/internal/csi" + "github.com/openshift/lvm-operator/internal/migration/microlvms" "github.com/spf13/cobra" topolvmcontrollers "github.com/topolvm/topolvm/controllers" "github.com/topolvm/topolvm/driver" @@ -158,6 +159,10 @@ func run(cmd *cobra.Command, _ []string, opts *Options) error { } } + if err := microlvms.NewCleanup(setupClient, operatorNamespace).RemovePreMicroLVMSComponents(ctx); err != nil { + return fmt.Errorf("failed to run pre 4.16 MicroLVMS cleanup: %w", err) + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: opts.Scheme, Metrics: metricsserver.Options{ diff --git a/cmd/vgmanager/vgmanager.go b/cmd/vgmanager/vgmanager.go index 93964f5b7..d5da46a20 100644 --- a/cmd/vgmanager/vgmanager.go +++ b/cmd/vgmanager/vgmanager.go @@ -38,7 +38,7 @@ import ( "github.com/openshift/lvm-operator/internal/controllers/vgmanager/lvmd" "github.com/openshift/lvm-operator/internal/controllers/vgmanager/wipefs" internalCSI "github.com/openshift/lvm-operator/internal/csi" - "github.com/openshift/lvm-operator/internal/tagging" + "github.com/openshift/lvm-operator/internal/migration/tagging" "github.com/spf13/cobra" topolvmClient "github.com/topolvm/topolvm/client" "github.com/topolvm/topolvm/controllers" diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 557f95668..d4240846c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -21,8 +21,12 @@ rules: resources: - deployments verbs: + - create + - delete - get - list + - patch + - update - watch - apiGroups: - apps diff --git a/internal/cluster/leaderelection.go b/internal/cluster/leaderelection.go index 4ce04984c..dd062d408 100644 --- a/internal/cluster/leaderelection.go +++ b/internal/cluster/leaderelection.go @@ -12,6 +12,8 @@ import ( // see https://kubernetes.io/docs/reference/labels-annotations-taints/#node-role-kubernetes-io-control-plane const ControlPlaneIDLabel = "node-role.kubernetes.io/control-plane" +const LeaseName = "1136b8a6.topolvm.io" + type LeaderElectionResolver interface { Resolve(ctx context.Context) (configv1.LeaderElection, error) } @@ -26,7 +28,7 @@ func NewLeaderElectionResolver( ) (LeaderElectionResolver, error) { defaultElectionConfig := leaderelection.LeaderElectionDefaulting(configv1.LeaderElection{ Disable: !enableLeaderElection, - }, operatorNamespace, "1136b8a6.topolvm.io") + }, operatorNamespace, LeaseName) return &nodeLookupSNOLeaderElection{ snoCheck: snoCheck, diff --git a/internal/controllers/lvmcluster/controller.go b/internal/controllers/lvmcluster/controller.go index 25a7fe5b3..d68471923 100644 --- a/internal/controllers/lvmcluster/controller.go +++ b/internal/controllers/lvmcluster/controller.go @@ -108,7 +108,7 @@ func (r *Reconciler) GetLogPassthroughOptions() *logpassthrough.Options { //+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmclusters,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get -//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmclusters/status,verbs=get;update;patch //+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmclusters/finalizers,verbs=update //+kubebuilder:rbac:groups=lvm.topolvm.io,resources=lvmvolumegroups,verbs=get;list;watch;create;update;patch;delete diff --git a/internal/migration/microlvms/cleanup.go b/internal/migration/microlvms/cleanup.go new file mode 100644 index 000000000..aa22e386c --- /dev/null +++ b/internal/migration/microlvms/cleanup.go @@ -0,0 +1,102 @@ +package microlvms + +import ( + "context" + "errors" + "fmt" + + "github.com/openshift/lvm-operator/internal/cluster" + appsv1 "k8s.io/api/apps/v1" + coordinationv1 "k8s.io/api/coordination/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +const ( + TopoLVMLegacyControllerName = "topolvm-controller" + TopoLVMLegacyNodeDaemonSetName = "topolvm-node" +) + +type Cleanup struct { + namespace string + client client.Client +} + +func NewCleanup(client client.Client, namespace string) *Cleanup { + return &Cleanup{ + namespace: namespace, + client: client, + } +} + +// RemovePreMicroLVMSComponents is a method of the `Cleanup` struct that performs cleanup tasks for the components +// that ran pre MicroLVMS, e.g. separate controllers or daemonsets. +func (c *Cleanup) RemovePreMicroLVMSComponents(ctx context.Context) error { + objs := []client.Object{ + // integrated into lvms operator + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: TopoLVMLegacyControllerName, + Namespace: c.namespace, + }, + }, + // integrated into vgmanager + &appsv1.DaemonSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: TopoLVMLegacyNodeDaemonSetName, + Namespace: c.namespace, + }, + }, + // replaced by Replace Deployment strategy without Lease + &coordinationv1.Lease{ + ObjectMeta: metav1.ObjectMeta{ + Name: cluster.LeaseName, + Namespace: c.namespace, + }, + }, + } + + results := make(chan error, len(objs)) + deleteImmediatelyIfExistsByIndex := func(i int) { + results <- c.deleteImmediatelyIfExists(ctx, objs[i]) + } + for i := range objs { + go deleteImmediatelyIfExistsByIndex(i) + } + + var errs []error + for i := 0; i < len(objs); i++ { + if err := <-results; err != nil { + errs = append(errs, err) + } + } + + if len(errs) > 0 { + return fmt.Errorf("failed to run pre 4.16 MicroLVMS cleanup: %w", errors.Join(errs...)) + } + + return nil +} + +func (c *Cleanup) deleteImmediatelyIfExists(ctx context.Context, obj client.Object) error { + gvk, _ := apiutil.GVKForObject(obj, c.client.Scheme()) + logger := log.FromContext(ctx).WithValues("gvk", gvk.String(), + "name", obj.GetName(), "namespace", obj.GetNamespace()) + + if err := c.client.Delete(ctx, obj, &client.DeleteOptions{ + GracePeriodSeconds: ptr.To(int64(0)), + }); err != nil { + if k8serrors.IsNotFound(err) { + logger.V(1).Info("not found, nothing to delete in cleanup.") + return nil + } + return fmt.Errorf("cleanup delete failed: %w", err) + } + + logger.Info("delete successful.") + return nil +} diff --git a/internal/migration/microlvms/cleanup_test.go b/internal/migration/microlvms/cleanup_test.go new file mode 100644 index 000000000..671be4fc4 --- /dev/null +++ b/internal/migration/microlvms/cleanup_test.go @@ -0,0 +1,93 @@ +package microlvms + +import ( + "context" + "errors" + "testing" + + "github.com/openshift/lvm-operator/internal/cluster" + appsv1 "k8s.io/api/apps/v1" + coordinationv1 "k8s.io/api/coordination/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" +) + +func TestRemovePreMicroLVMSComponents(t *testing.T) { + tests := []struct { + name string + exist bool + wantErr bool + }{ + { + name: "objects dont exist anymore (post-migration)", + exist: false, + wantErr: false, + }, + { + name: "objects still exist (pre-migration)", + exist: true, + wantErr: false, + }, + { + name: "objects exist but delete fails", + exist: true, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + namespace := "openshift-storage" + fakeClientBuilder := fake.NewClientBuilder(). + WithScheme(setUpScheme()). + WithObjects(setUpObjs(tt.exist, namespace)...) + if tt.wantErr { + fakeClientBuilder.WithInterceptorFuncs(interceptor.Funcs{ + Delete: func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.DeleteOption) error { + return errors.New("delete failed") + }, + }) + } + cleanup := NewCleanup(fakeClientBuilder.Build(), namespace) + if err := cleanup.RemovePreMicroLVMSComponents(context.Background()); (err != nil) != tt.wantErr { + t.Errorf("RemovePreMicroLVMSComponents() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func setUpScheme() *runtime.Scheme { + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = coordinationv1.AddToScheme(scheme) + return scheme +} + +func setUpObjs(exist bool, namespace string) []client.Object { + if exist { + return nil + } + return []client.Object{ + &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{ + Name: TopoLVMLegacyControllerName, + Namespace: namespace, + }, + }, + &appsv1.DaemonSet{ + ObjectMeta: v1.ObjectMeta{ + Name: TopoLVMLegacyNodeDaemonSetName, + Namespace: namespace, + }, + }, + &coordinationv1.Lease{ + ObjectMeta: v1.ObjectMeta{ + Name: cluster.LeaseName, + Namespace: namespace, + }, + }, + } +} diff --git a/internal/tagging/tagging.go b/internal/migration/tagging/tagging.go similarity index 100% rename from internal/tagging/tagging.go rename to internal/migration/tagging/tagging.go diff --git a/internal/tagging/tagging_test.go b/internal/migration/tagging/tagging_test.go similarity index 98% rename from internal/tagging/tagging_test.go rename to internal/migration/tagging/tagging_test.go index 2cd356291..4fbc0da87 100644 --- a/internal/tagging/tagging_test.go +++ b/internal/migration/tagging/tagging_test.go @@ -7,7 +7,7 @@ import ( "github.com/openshift/lvm-operator/api/v1alpha1" "github.com/openshift/lvm-operator/internal/controllers/vgmanager/lvm" lvmmocks "github.com/openshift/lvm-operator/internal/controllers/vgmanager/lvm/mocks" - "github.com/openshift/lvm-operator/internal/tagging" + "github.com/openshift/lvm-operator/internal/migration/tagging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock"