Skip to content

Commit 95572bc

Browse files
Merge pull request #983 from dinhxuanvu/removeDeprecated
Bug 1746270: Remove deprecated CRD's stored versions to allow CRD update
2 parents 5a5389c + 4bb8526 commit 95572bc

File tree

3 files changed

+435
-19
lines changed

3 files changed

+435
-19
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,17 +1127,23 @@ func (o *Operator) ResolvePlan(plan *v1alpha1.InstallPlan) error {
11271127
return nil
11281128
}
11291129

1130-
// Ensure all existing served versions are present in new CRD
1131-
func ensureCRDVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error {
1132-
newCRDVersions := map[string]struct{}{}
1130+
func getCRDVersionsMap(crd *v1beta1ext.CustomResourceDefinition) map[string]struct{} {
1131+
versionsMap := map[string]struct{}{}
11331132

1134-
for _, newVersion := range newCRD.Spec.Versions {
1135-
newCRDVersions[newVersion.Name] = struct{}{}
1133+
for _, version := range crd.Spec.Versions {
1134+
versionsMap[version.Name] = struct{}{}
11361135
}
1137-
if newCRD.Spec.Version != "" {
1138-
newCRDVersions[newCRD.Spec.Version] = struct{}{}
1136+
if crd.Spec.Version != "" {
1137+
versionsMap[crd.Spec.Version] = struct{}{}
11391138
}
11401139

1140+
return versionsMap
1141+
}
1142+
1143+
// Ensure all existing served versions are present in new CRD
1144+
func ensureCRDVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error {
1145+
newCRDVersions := getCRDVersionsMap(newCRD)
1146+
11411147
for _, oldVersion := range oldCRD.Spec.Versions {
11421148
if oldVersion.Served {
11431149
_, ok := newCRDVersions[oldVersion.Name]
@@ -1202,10 +1208,36 @@ func (o *Operator) validateExistingCRs(gvr schema.GroupVersionResource, newCRD *
12021208
return fmt.Errorf("error validating custom resource against new schema %#v: %s", newCRD.Spec.Validation, err)
12031209
}
12041210
}
1205-
12061211
return nil
12071212
}
12081213

1214+
// Attempt to remove stored versions that have been deprecated before allowing
1215+
// those versions to be removed from the new CRD.
1216+
// The function may not always succeed as storedVersions requires at least one
1217+
// version. If there is only stored version, it won't be removed until a new
1218+
// stored version is added.
1219+
func removeDeprecatedStoredVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) []string {
1220+
// StoredVersions requires to have at least one version.
1221+
if len(oldCRD.Status.StoredVersions) <= 1 {
1222+
return nil
1223+
}
1224+
1225+
newStoredVersions := []string{}
1226+
newCRDVersions := getCRDVersionsMap(newCRD)
1227+
for _, v := range oldCRD.Status.StoredVersions {
1228+
_, ok := newCRDVersions[v]
1229+
if ok {
1230+
newStoredVersions = append(newStoredVersions, v)
1231+
}
1232+
}
1233+
1234+
if len(newStoredVersions) < 1 {
1235+
return nil
1236+
} else {
1237+
return newStoredVersions
1238+
}
1239+
}
1240+
12091241
// ExecutePlan applies a planned InstallPlan to a namespace.
12101242
func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
12111243
if plan.Status.Phase != v1alpha1.InstallPlanPhaseInstalling {
@@ -1255,7 +1287,9 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
12551287
if k8serrors.IsAlreadyExists(err) {
12561288
currentCRD, _ := o.lister.APIExtensionsV1beta1().CustomResourceDefinitionLister().Get(crd.GetName())
12571289
// Compare 2 CRDs to see if it needs to be updatetd
1258-
if !reflect.DeepEqual(crd, *currentCRD) {
1290+
if !(reflect.DeepEqual(crd.Spec.Version, currentCRD.Spec.Version) &&
1291+
reflect.DeepEqual(crd.Spec.Versions, currentCRD.Spec.Versions) &&
1292+
reflect.DeepEqual(crd.Spec.Validation, currentCRD.Spec.Validation)) {
12591293
// Verify CRD ownership, only attempt to update if
12601294
// CRD has only one owner
12611295
// Example: provided=database.coreos.com/v1alpha1/EtcdCluster
@@ -1266,11 +1300,6 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
12661300
crd.SetResourceVersion(currentCRD.GetResourceVersion())
12671301
if len(matchedCSV) == 1 {
12681302
o.logger.Debugf("Found one owner for CRD %v", crd)
1269-
1270-
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
1271-
if err != nil {
1272-
return errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
1273-
}
12741303
} else if len(matchedCSV) > 1 {
12751304
o.logger.Debugf("Found multiple owners for CRD %v", crd)
12761305

@@ -1282,11 +1311,21 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
12821311
if err = o.validateCustomResourceDefinition(currentCRD, &crd); err != nil {
12831312
return errorwrap.Wrapf(err, "error validating existing CRs agains new CRD's schema: %s", step.Resource.Name)
12841313
}
1285-
1286-
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
1314+
}
1315+
// Remove deprecated version in CRD storedVersions
1316+
storeVersions := removeDeprecatedStoredVersions(currentCRD, &crd)
1317+
if storeVersions != nil {
1318+
currentCRD.Status.StoredVersions = storeVersions
1319+
resultCRD, err := o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().UpdateStatus(currentCRD)
12871320
if err != nil {
1288-
return errorwrap.Wrapf(err, "error update CRD: %s", step.Resource.Name)
1321+
return errorwrap.Wrapf(err, "error updating CRD's status: %s", step.Resource.Name)
12891322
}
1323+
crd.SetResourceVersion(resultCRD.GetResourceVersion())
1324+
}
1325+
// Update CRD to new version
1326+
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
1327+
if err != nil {
1328+
return errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
12901329
}
12911330
}
12921331
// If it already existed, mark the step as Present.

pkg/controller/operators/catalog/operator_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,105 @@ func TestEnsureCRDVersions(t *testing.T) {
235235
}
236236
}
237237

238+
func TestRemoveDeprecatedStoredVersions(t *testing.T) {
239+
mainCRDPlural := "ins-main-test"
240+
241+
currentVersions := []v1beta1.CustomResourceDefinitionVersion{
242+
{
243+
Name: "v1alpha1",
244+
Served: true,
245+
Storage: false,
246+
},
247+
{
248+
Name: "v1alpha2",
249+
Served: true,
250+
Storage: true,
251+
},
252+
}
253+
254+
newVersions := []v1beta1.CustomResourceDefinitionVersion{
255+
{
256+
Name: "v1alpha2",
257+
Served: true,
258+
Storage: false,
259+
},
260+
{
261+
Name: "v1beta1",
262+
Served: true,
263+
Storage: true,
264+
},
265+
}
266+
267+
crdStatusStoredVersions := v1beta1.CustomResourceDefinitionStatus{
268+
StoredVersions: []string{},
269+
}
270+
271+
tests := []struct {
272+
name string
273+
oldCRD v1beta1.CustomResourceDefinition
274+
newCRD v1beta1.CustomResourceDefinition
275+
expectedResult []string
276+
}{
277+
{
278+
name: "only one stored version exists",
279+
oldCRD: func() v1beta1.CustomResourceDefinition {
280+
oldCRD := crd(mainCRDPlural)
281+
oldCRD.Spec.Version = ""
282+
oldCRD.Spec.Versions = currentVersions
283+
oldCRD.Status = crdStatusStoredVersions
284+
oldCRD.Status.StoredVersions = []string{"v1alpha1"}
285+
return oldCRD
286+
}(),
287+
newCRD: func() v1beta1.CustomResourceDefinition {
288+
newCRD := crd(mainCRDPlural)
289+
newCRD.Spec.Version = ""
290+
newCRD.Spec.Versions = newVersions
291+
return newCRD
292+
}(),
293+
expectedResult: nil,
294+
},
295+
{
296+
name: "multiple stored versions with one deprecated version",
297+
oldCRD: func() v1beta1.CustomResourceDefinition {
298+
oldCRD := crd(mainCRDPlural)
299+
oldCRD.Spec.Version = ""
300+
oldCRD.Spec.Versions = currentVersions
301+
oldCRD.Status.StoredVersions = []string{"v1alpha1", "v1alpha2"}
302+
return oldCRD
303+
}(),
304+
newCRD: func() v1beta1.CustomResourceDefinition {
305+
newCRD := crd(mainCRDPlural)
306+
newCRD.Spec.Version = ""
307+
newCRD.Spec.Versions = newVersions
308+
return newCRD
309+
}(),
310+
expectedResult: []string{"v1alpha2"},
311+
},
312+
{
313+
name: "multiple stored versions with all deprecated version",
314+
oldCRD: func() v1beta1.CustomResourceDefinition {
315+
oldCRD := crd(mainCRDPlural)
316+
oldCRD.Spec.Version = ""
317+
oldCRD.Spec.Versions = currentVersions
318+
oldCRD.Status.StoredVersions = []string{"v1alpha1", "v1alpha3"}
319+
return oldCRD
320+
}(),
321+
newCRD: func() v1beta1.CustomResourceDefinition {
322+
newCRD := crd(mainCRDPlural)
323+
newCRD.Spec.Version = ""
324+
newCRD.Spec.Versions = newVersions
325+
return newCRD
326+
}(),
327+
expectedResult: nil,
328+
},
329+
}
330+
331+
for _, tt := range tests {
332+
resultCRD := removeDeprecatedStoredVersions(&tt.oldCRD, &tt.newCRD)
333+
require.Equal(t, tt.expectedResult, resultCRD)
334+
}
335+
}
336+
238337
func TestExecutePlan(t *testing.T) {
239338
namespace := "ns"
240339

0 commit comments

Comments
 (0)