Skip to content

Commit 3503c71

Browse files
committed
OCPBUGS-52556: Remove SVM console instance if v1alpha1 ConsolePlugin version in present in the CRDs status
1 parent 5e127e5 commit 3503c71

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed

manifests/03-rbac-role-cluster.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ rules:
135135
- get
136136
- list
137137
- watch
138+
- apiGroups:
139+
- migration.k8s.io
140+
resources:
141+
- storageversionmigrations
142+
verbs:
143+
- get
144+
- list
145+
- watch
146+
- delete
138147
---
139148
kind: ClusterRole
140149
apiVersion: rbac.authorization.k8s.io/v1
@@ -151,10 +160,12 @@ rules:
151160
- apiextensions.k8s.io
152161
resources:
153162
- customresourcedefinitions
163+
- customresourcedefinitions/status
154164
verbs:
155165
- get
156166
- list
157167
- watch
168+
- patch
158169
- apiGroups:
159170
- admissionregistration.k8s.io
160171
resources:
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package storageversionmigration
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9+
"k8s.io/apimachinery/pkg/runtime/schema"
10+
"k8s.io/apimachinery/pkg/types"
11+
"k8s.io/apimachinery/pkg/util/json"
12+
"k8s.io/client-go/dynamic"
13+
"k8s.io/client-go/dynamic/dynamicinformer"
14+
"k8s.io/klog/v2"
15+
16+
"github.com/openshift/console-operator/pkg/console/status"
17+
"github.com/openshift/library-go/pkg/controller/factory"
18+
"github.com/openshift/library-go/pkg/operator/events"
19+
"github.com/openshift/library-go/pkg/operator/v1helpers"
20+
)
21+
22+
const (
23+
storageVersionMigrationName = "console-plugin-storage-version-migration"
24+
consolePluginCRDName = "consoleplugins.console.openshift.io"
25+
maxRetries = 5
26+
retryDelay = 2 * time.Second
27+
)
28+
29+
var (
30+
storageVersionMigrationGVR = schema.GroupVersionResource{
31+
Group: "migration.k8s.io",
32+
Version: "v1alpha1",
33+
Resource: "storageversionmigrations",
34+
}
35+
crdGVR = schema.GroupVersionResource{
36+
Group: "apiextensions.k8s.io",
37+
Version: "v1",
38+
Resource: "customresourcedefinitions",
39+
}
40+
)
41+
42+
type StorageVersionMigrationController struct {
43+
dynamicClient dynamic.Interface
44+
operatorClient v1helpers.OperatorClient
45+
}
46+
47+
func NewStorageVersionMigrationController(
48+
operatorClient v1helpers.OperatorClient,
49+
dynamicClient dynamic.Interface,
50+
dynamicInformers dynamicinformer.DynamicSharedInformerFactory,
51+
recorder events.Recorder,
52+
) factory.Controller {
53+
c := &StorageVersionMigrationController{
54+
dynamicClient: dynamicClient,
55+
operatorClient: operatorClient,
56+
}
57+
58+
return factory.New().
59+
WithInformers(
60+
dynamicInformers.ForResource(storageVersionMigrationGVR).Informer(),
61+
).
62+
WithSync(c.sync).
63+
ToController("StorageVersionMigrationController", recorder)
64+
}
65+
66+
func (c *StorageVersionMigrationController) sync(ctx context.Context, syncContext factory.SyncContext) error {
67+
statusHandler := status.NewStatusHandler(c.operatorClient)
68+
69+
// Get the StorageVersionMigration instance
70+
svm, err := c.dynamicClient.Resource(storageVersionMigrationGVR).Get(ctx, storageVersionMigrationName, metav1.GetOptions{})
71+
if err != nil {
72+
klog.Errorf("Failed to get StorageVersionMigration: %v", err)
73+
return statusHandler.FlushAndReturn(err)
74+
}
75+
76+
// Check if the migration has succeeded with retry logic
77+
succeeded, err := c.hasSucceededConditionWithRetry(svm)
78+
if err != nil {
79+
klog.Errorf("Failed to check conditions for StorageVersionMigration after %d retries: %v", maxRetries, err)
80+
return statusHandler.FlushAndReturn(err)
81+
}
82+
83+
if !succeeded {
84+
klog.V(4).Infof("StorageVersionMigration has not succeeded yet")
85+
// Delete the StorageVersionMigration if it has not succeeded yet
86+
if err := c.deleteStorageVersionMigration(ctx); err != nil {
87+
statusHandler.AddCondition(status.HandleDegraded("StorageVersionMigration", "FailedDeleteSVM", err))
88+
return statusHandler.FlushAndReturn(err)
89+
}
90+
return statusHandler.FlushAndReturn(nil)
91+
}
92+
93+
klog.Infof("StorageVersionMigration has succeeded, setting ConsolePlugin CRD storedVersions to v1")
94+
95+
// Set CRD storedVersions to v1
96+
if err := c.removeV1Alpha1FromCRD(ctx); err != nil {
97+
statusHandler.AddCondition(status.HandleDegraded("StorageVersionMigration", "FailedPatchCRD", err))
98+
return statusHandler.FlushAndReturn(err)
99+
}
100+
101+
return statusHandler.FlushAndReturn(nil)
102+
}
103+
104+
// hasSucceededConditionWithRetry checks if the StorageVersionMigration has succeeded with retry logic
105+
func (c *StorageVersionMigrationController) hasSucceededConditionWithRetry(svm *unstructured.Unstructured) (bool, error) {
106+
var lastErr error
107+
for attempt := 1; attempt <= maxRetries; attempt++ {
108+
succeeded, err := c.hasSucceededCondition(svm)
109+
if err == nil {
110+
return succeeded, nil
111+
}
112+
113+
lastErr = err
114+
klog.Warningf("Attempt %d/%d failed to check StorageVersionMigration conditions: %v", attempt, maxRetries, err)
115+
116+
if attempt < maxRetries {
117+
klog.V(4).Infof("Retrying in %v...", retryDelay)
118+
time.Sleep(retryDelay)
119+
}
120+
}
121+
122+
return false, lastErr
123+
}
124+
125+
// hasSucceededCondition checks if the StorageVersionMigration has a 'Succeeded' condition with status 'True'
126+
func (c *StorageVersionMigrationController) hasSucceededCondition(svm *unstructured.Unstructured) (bool, error) {
127+
conditions, found, err := unstructured.NestedSlice(svm.Object, "status", "conditions")
128+
if err != nil {
129+
return false, err
130+
}
131+
if !found {
132+
return false, nil
133+
}
134+
135+
for _, condition := range conditions {
136+
conditionMap, ok := condition.(map[string]interface{})
137+
if !ok {
138+
continue
139+
}
140+
141+
conditionType, typeFound := conditionMap["type"].(string)
142+
conditionStatus, statusFound := conditionMap["status"].(string)
143+
144+
if typeFound && statusFound && conditionType == "Succeeded" && conditionStatus == "True" {
145+
return true, nil
146+
}
147+
}
148+
149+
return false, nil
150+
}
151+
152+
// removeV1Alpha1FromCRD removes 'v1alpha1' from the ConsolePlugin CRD's storedVersions
153+
func (c *StorageVersionMigrationController) removeV1Alpha1FromCRD(ctx context.Context) error {
154+
// Get the ConsolePlugin CRD
155+
crd, err := c.dynamicClient.Resource(crdGVR).Get(ctx, consolePluginCRDName, metav1.GetOptions{})
156+
if err != nil {
157+
klog.Errorf("Failed to get ConsolePlugin CRD: %v", err)
158+
return err
159+
}
160+
161+
// Verify CRD exists and has status
162+
_, found, err := unstructured.NestedMap(crd.Object, "status")
163+
if err != nil {
164+
klog.Errorf("Failed to get status for ConsolePlugin CRD: %v", err)
165+
return err
166+
}
167+
if !found {
168+
klog.Info("No status found for ConsolePlugin CRD")
169+
return nil
170+
}
171+
172+
// Set storedVersions to only contain v1
173+
newStoredVersions := []string{"v1"}
174+
175+
// Create and apply patch
176+
return c.patchCRDStoredVersions(ctx, newStoredVersions)
177+
}
178+
179+
// patchCRDStoredVersions applies a patch to update the CRD's storedVersions
180+
func (c *StorageVersionMigrationController) patchCRDStoredVersions(ctx context.Context, newStoredVersions []string) error {
181+
patch := map[string]interface{}{
182+
"status": map[string]interface{}{
183+
"storedVersions": newStoredVersions,
184+
},
185+
}
186+
187+
patchBytes, err := json.Marshal(patch)
188+
if err != nil {
189+
klog.Errorf("Failed to marshal patch for ConsolePlugin CRD: %v", err)
190+
return err
191+
}
192+
193+
// Apply the patch
194+
_, err = c.dynamicClient.Resource(crdGVR).Patch(ctx, consolePluginCRDName, types.MergePatchType, patchBytes, metav1.PatchOptions{})
195+
if err != nil {
196+
klog.Errorf("Failed to patch ConsolePlugin CRD: %v", err)
197+
return err
198+
}
199+
200+
klog.Infof("Successfully set ConsolePlugin CRD storedVersions to v1")
201+
return nil
202+
}
203+
204+
// deleteStorageVersionMigration deletes the StorageVersionMigration resource
205+
func (c *StorageVersionMigrationController) deleteStorageVersionMigration(ctx context.Context) error {
206+
klog.Infof("Deleting StorageVersionMigration")
207+
err := c.dynamicClient.Resource(storageVersionMigrationGVR).Delete(ctx, storageVersionMigrationName, metav1.DeleteOptions{})
208+
if err != nil {
209+
klog.Errorf("Failed to delete StorageVersionMigration: %v", err)
210+
return err
211+
}
212+
return nil
213+
}

pkg/console/starter/starter.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
pdb "github.com/openshift/console-operator/pkg/console/controllers/poddisruptionbudget"
3737
"github.com/openshift/console-operator/pkg/console/controllers/route"
3838
"github.com/openshift/console-operator/pkg/console/controllers/service"
39+
"github.com/openshift/console-operator/pkg/console/controllers/storageversionmigration"
3940
upgradenotification "github.com/openshift/console-operator/pkg/console/controllers/upgradenotification"
4041
"github.com/openshift/console-operator/pkg/console/controllers/util"
4142
"github.com/openshift/library-go/pkg/controller/controllercmd"
@@ -555,6 +556,14 @@ func RunOperator(ctx context.Context, controllerContext *controllercmd.Controlle
555556
recorder,
556557
)
557558

559+
// Create the StorageVersionMigration controller
560+
storageversionmigrationController := storageversionmigration.NewStorageVersionMigrationController(
561+
operatorClient,
562+
dynamicClient,
563+
dynamicInformers,
564+
recorder,
565+
)
566+
558567
configUpgradeableController := unsupportedconfigoverridescontroller.NewUnsupportedConfigOverridesController("UnsupportedConfigOverridesController", operatorClient, controllerContext.EventRecorder)
559568
logLevelController := loglevel.NewClusterOperatorLoggingController(operatorClient, controllerContext.EventRecorder)
560569
managementStateController := managementstatecontroller.NewOperatorManagementStateController(api.ClusterOperatorName, operatorClient, controllerContext.EventRecorder)
@@ -603,6 +612,7 @@ func RunOperator(ctx context.Context, controllerContext *controllercmd.Controlle
603612
cliOIDCClientStatusController,
604613
upgradeNotificationController,
605614
staleConditionsController,
615+
storageversionmigrationController,
606616
} {
607617
go controller.Run(ctx, 1)
608618
}

0 commit comments

Comments
 (0)