Skip to content

Commit 4ae455d

Browse files
committed
wip: add crd storage migration to safely deprecate old storage versions
1 parent 77be93f commit 4ae455d

File tree

2 files changed

+89
-6
lines changed

2 files changed

+89
-6
lines changed

pkg/controller/operators/catalog/step.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,32 @@ func (b *builder) NewCRDV1Step(client apiextensionsv1client.ApiextensionsV1Inter
133133
logger.Debugf("Found one owner for CRD %v", crd)
134134
} else if len(matchedCSV) > 1 {
135135
logger.Debugf("Found multiple owners for CRD %v", crd)
136-
137136
if err = validateV1CRDCompatibility(b.dynamicClient, currentCRD, crd); err != nil {
138137
return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error validating existing CRs agains new CRD's schema: %s", step.Resource.Name)
139138
}
140139
}
141140

142-
// TODO ensure stored version compatibility
141+
// (optional) run migration to new stored version to ensure no data loss during CRD upgrade
142+
if crdlib.RunStorageMigration(currentCRD, crd) {
143+
// add new storage version to current CRD
144+
currentCRD.Status.StoredVersions = append(currentCRD.Status.StoredVersions, crdlib.GetNewStorageVersion(crd))
145+
_, err := client.CustomResourceDefinitions().UpdateStatus(context.TODO(), crd, metav1.UpdateOptions{})
146+
if err != nil {
147+
return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error updating CRD status: %s", step.Resource.Name)
148+
}
149+
// list all CRs corresponding to the CRD
150+
kind := fmt.Sprintf("kind:%s", currentCRD.Spec.Names.Kind)
151+
// TODO get all CRs via rest client
152+
b.logger.Infof("updating %s", kind)
153+
// remove old storage version from CRD
154+
newVersions := currentCRD.Status.StoredVersions[1:] //remove first (oldest) version
155+
currentCRD.Status.StoredVersions = newVersions
156+
_, err = client.CustomResourceDefinitions().UpdateStatus(context.TODO(), crd, metav1.UpdateOptions{})
157+
if err != nil {
158+
return v1alpha1.StepStatusUnknown, errorwrap.Wrapf(err, "error updating CRD status: %s", step.Resource.Name)
159+
}
160+
}
161+
143162
// Update CRD to new version
144163
_, err = client.CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{})
145164
if err != nil {

pkg/lib/crd/version.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,70 @@ func StoredVersions(obj runtime.Object) []string {
7676
return nil
7777
}
7878

79-
// NewStoredVersions returns all the versions that are stored in both the new and old CRD.
80-
// If some versions are no longer stored in the new CRD then OLM must migrate existing CRs at the old storage version.
81-
func NewStoredVersions(oldCRD runtime.Object, newCRD runtime.Object) ([]string, error) {
79+
// RunStorageMigration determines whether the new CRD changes the storage version of the existing CRD.
80+
// If true, OLM must run a migration process to ensure all CRs can be stored at the new version.
81+
// See https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definition-versioning/#upgrade-existing-objects-to-a-new-stored-version
82+
func RunStorageMigration(oldCRD runtime.Object, newCRD runtime.Object) bool {
83+
newStoredVersion := make(map[string]struct{})
84+
switch crd := newCRD.(type) {
85+
case *apiextensionsv1.CustomResourceDefinition:
86+
for _, version := range crd.Spec.Versions {
87+
if version.Storage {
88+
newStoredVersion[version.Name] = struct{}{}
89+
}
90+
}
91+
case *apiextensionsv1beta1.CustomResourceDefinition:
92+
for _, version := range crd.Spec.Versions {
93+
if version.Storage {
94+
newStoredVersion[version.Name] = struct{}{}
95+
}
96+
}
97+
}
98+
99+
// find old storage versions by inspect the status field of the existing on-cluster CRD
100+
oldStoredVersions := make(map[string]struct{})
101+
switch crd := oldCRD.(type) {
102+
case *apiextensionsv1.CustomResourceDefinition:
103+
for _, version := range crd.Status.StoredVersions {
104+
oldStoredVersions[version] = struct{}{}
105+
}
106+
case *apiextensionsv1beta1.CustomResourceDefinition:
107+
for _, version := range crd.Status.StoredVersions {
108+
oldStoredVersions[version] = struct{}{}
109+
}
110+
}
111+
112+
for name := range oldStoredVersions {
113+
if _, ok := newStoredVersion[name]; ok {
114+
// new storage version exists in old CRD present on the cluster
115+
// no need to run migration
116+
return false
117+
}
118+
}
119+
return true
120+
}
121+
122+
func GetNewStorageVersion(crd runtime.Object) string {
123+
switch crd := crd.(type) {
124+
case *apiextensionsv1.CustomResourceDefinition:
125+
for _, version := range crd.Spec.Versions {
126+
if version.Storage {
127+
return version.Name
128+
}
129+
}
130+
case *apiextensionsv1beta1.CustomResourceDefinition:
131+
for _, version := range crd.Spec.Versions {
132+
if version.Storage {
133+
return version.Name
134+
}
135+
}
136+
}
137+
return ""
138+
}
139+
140+
// DeprecatedStorageVersions returns all the versions that are in the old CRD but no longer in the new CRD.
141+
// These versions should be removed after the storage upgrade procedure.
142+
func DeprecatedStorageVersions(oldCRD runtime.Object, newCRD runtime.Object) ([]string, error) {
82143
var newStoredVersions []string
83144

84145
newCRDVersions, err := ResourceVersions(newCRD)
@@ -93,6 +154,9 @@ func NewStoredVersions(oldCRD runtime.Object, newCRD runtime.Object) ([]string,
93154
}
94155
}
95156

96-
return newStoredVersions, nil
157+
var deprecatedVersions []string
158+
159+
160+
return deprecatedVersions, nil
97161
}
98162

0 commit comments

Comments
 (0)