Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug 1807615: Add extra check for v1alpha CRD #40

Merged
merged 1 commit into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 9 additions & 2 deletions pkg/operator/operator.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package operator

import (
"errors"
"fmt"
"time"

Expand Down Expand Up @@ -177,11 +178,16 @@ func (c *csiSnapshotOperator) sync() error {

func (c *csiSnapshotOperator) updateSyncError(status *operatorv1.OperatorStatus, err error) {
if err != nil {
degradedReason := "OperatorSync"
var errAlpha *AlphaCRDError
if errors.As(err, &errAlpha) {
degradedReason = "AlphaCRDsExist"
}
v1helpers.SetOperatorCondition(&status.Conditions,
operatorv1.OperatorCondition{
Type: operatorv1.OperatorStatusTypeDegraded,
Status: operatorv1.ConditionTrue,
Reason: "OperatorSync",
Reason: degradedReason,
Message: err.Error(),
})
} else {
Expand All @@ -195,7 +201,8 @@ func (c *csiSnapshotOperator) updateSyncError(status *operatorv1.OperatorStatus,

func (c *csiSnapshotOperator) handleSync(instance *operatorv1.CSISnapshotController) error {
if err := c.syncCustomResourceDefinitions(); err != nil {
return fmt.Errorf("failed to sync CRDs: %s", err)
// Pass through AlphaCRDError via %w
return fmt.Errorf("failed to sync CRDs: %w", err)
}

deployment, err := c.syncDeployment(instance)
Expand Down
134 changes: 133 additions & 1 deletion pkg/operator/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func newOperator(test operatorTest) *testContext {
extAPIInformerFactory := apiextinformers.NewSharedInformerFactory(extAPIClient, 0)
// Fill the informer
for i := range test.initialObjects.crds {
extAPIInformerFactory.Apiextensions().V1().CustomResourceDefinitions().Informer().GetIndexer().Add(test.initialObjects.crds[i])
extAPIInformerFactory.Apiextensions().V1beta1().CustomResourceDefinitions().Informer().GetIndexer().Add(test.initialObjects.crds[i])
}
if test.reactors.crds != nil {
test.reactors.crds(extAPIClient, extAPIInformerFactory)
Expand Down Expand Up @@ -317,6 +317,86 @@ func withEstablishedConditions(instance *apiextv1beta1.CustomResourceDefinition)
return instance
}

func getAlphaCRD(crdName string) *apiextv1beta1.CustomResourceDefinition {
var crdFile string
switch crdName {
case "VolumeSnapshot":
crdFile = `
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: volumesnapshots.snapshot.storage.k8s.io
spec:
conversion:
strategy: None
group: snapshot.storage.k8s.io
names:
kind: VolumeSnapshot
listKind: VolumeSnapshotList
plural: volumesnapshots
singular: volumesnapshot
preserveUnknownFields: true
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
`

case "VolumeSnapshotContent":
crdFile = `
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: volumesnapshotcontents.snapshot.storage.k8s.io
spec:
conversion:
strategy: None
group: snapshot.storage.k8s.io
names:
kind: VolumeSnapshotContent
listKind: VolumeSnapshotContentList
plural: volumesnapshotcontents
singular: volumesnapshotcontent
preserveUnknownFields: true
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
`
case "VolumeSnapshotClass":
crdFile = `
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: volumesnapshotclasses.snapshot.storage.k8s.io
spec:
conversion:
strategy: None
group: snapshot.storage.k8s.io
names:
kind: VolumeSnapshotClass
listKind: VolumeSnapshotClassList
plural: volumesnapshotclasses
singular: volumesnapshotclass
preserveUnknownFields: true
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status:
`
default:
panic(crdName)
}
return resourceread.ReadCustomResourceDefinitionV1Beta1OrDie([]byte(crdFile))
}

// Optional reactor that sets Established condition to True. It's needed by the operator that polls for CRDs until they get the condition
func addCRDEstablishedRector(client *fakeextapi.Clientset, informer apiextinformers.SharedInformerFactory) {
client.PrependReactor("*", "customresourcedefinitions", func(action core.Action) (handled bool, ret runtime.Object, err error) {
Expand Down Expand Up @@ -536,6 +616,58 @@ func TestSync(t *testing.T) {
},
expectErr: true,
},
{
// error: v1alpha1 VolumeSnapshot already exists
name: "v1alpha1 VolumeSnapshot",
image: defaultImage,
initialObjects: testObjects{
csiSnapshotController: csiSnapshotController(),
crds: []*apiextv1beta1.CustomResourceDefinition{getAlphaCRD("VolumeSnapshot")},
},
expectedObjects: testObjects{
crds: []*apiextv1beta1.CustomResourceDefinition{getAlphaCRD("VolumeSnapshot")},
csiSnapshotController: csiSnapshotController(withTrueConditions(opv1.OperatorStatusTypeDegraded)),
},
expectErr: true,
reactors: testReactors{
crds: addCRDEstablishedRector,
},
},
{
// error: v1alpha1 VolumeSnapshotContent already exists
name: "v1alpha1 VolumeSnapshotContent",
image: defaultImage,
initialObjects: testObjects{
csiSnapshotController: csiSnapshotController(),
crds: []*apiextv1beta1.CustomResourceDefinition{getAlphaCRD("VolumeSnapshotContent")},
},
expectedObjects: testObjects{
crds: []*apiextv1beta1.CustomResourceDefinition{getAlphaCRD("VolumeSnapshotContent")},
csiSnapshotController: csiSnapshotController(withTrueConditions(opv1.OperatorStatusTypeDegraded)),
},
expectErr: true,
reactors: testReactors{
crds: addCRDEstablishedRector,
},
},
{
// error: v1alpha1 VolumeSnapshotClass already exists
name: "v1alpha1 VolumeSnapshotClass",
image: defaultImage,
initialObjects: testObjects{
csiSnapshotController: csiSnapshotController(),
crds: []*apiextv1beta1.CustomResourceDefinition{getAlphaCRD("VolumeSnapshotClass")},
},
expectedObjects: testObjects{
crds: []*apiextv1beta1.CustomResourceDefinition{getAlphaCRD("VolumeSnapshotClass")},
csiSnapshotController: csiSnapshotController(withTrueConditions(opv1.OperatorStatusTypeDegraded)),
},
expectErr: true,
reactors: testReactors{
crds: addCRDEstablishedRector,
},
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a case with v1beta1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// TODO: more error cases? Deployment creation fails and things like that?
}

Expand Down
37 changes: 37 additions & 0 deletions pkg/operator/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

appsv1 "k8s.io/api/apps/v1"
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/wait"

operatorv1 "github.com/openshift/api/operator/v1"
Expand All @@ -29,7 +30,18 @@ var (
customResourceReadyTimeout = 10 * time.Minute
)

type AlphaCRDError struct {
alphaCRDs []string
}

func (a *AlphaCRDError) Error() string {
return fmt.Sprintf("cluster-csi-snapshot-controller-operator does not support v1alpha1 version of snapshot CRDs %s installed by user or 3rd party controller", strings.Join(a.alphaCRDs, ", "))
}

func (c *csiSnapshotOperator) syncCustomResourceDefinitions() error {
if err := c.checkAlphaCRDs(); err != nil {
return err
}
for _, file := range crds {
crd := resourceread.ReadCustomResourceDefinitionV1Beta1OrDie(generated.MustAsset(file))
_, updated, err := resourceapply.ApplyCustomResourceDefinition(c.crdClient.ApiextensionsV1beta1(), c.eventRecorder, crd)
Expand Down Expand Up @@ -70,6 +82,31 @@ func (c *csiSnapshotOperator) waitForCustomResourceDefinition(resource *apiextv1
return nil
}

// checkCRDAlpha checks if v1alpha1 version of the CRD exists and returns human-friendly error if so.
// This happens during update from 4.3 cluster that may use v1alpha1 CRD version installed by cluster admin.
func (c *csiSnapshotOperator) checkAlphaCRDs() error {
var alphas []string
for _, file := range crds {
crd := resourceread.ReadCustomResourceDefinitionV1Beta1OrDie(generated.MustAsset(file))
oldCRD, err := c.crdLister.Get(crd.Name)
if err != nil {
if errors.IsNotFound(err) {
continue
}
return fmt.Errorf("error getting CustomResourceDefinition %s: %v", crd.Name, err)
}
for _, version := range oldCRD.Spec.Versions {
if version.Name == "v1alpha1" {
alphas = append(alphas, oldCRD.Name)
}
}
}
if len(alphas) != 0 {
return &AlphaCRDError{alphas}
}
return nil
}

func (c *csiSnapshotOperator) syncDeployment(instance *operatorv1.CSISnapshotController) (*appsv1.Deployment, error) {
deploy := c.getExpectedDeployment(instance)

Expand Down