Skip to content

Commit 24c59dc

Browse files
committed
Add e2e test case for single owner CRD update
Signed-off-by: Vu Dinh <[email protected]>
1 parent 924c565 commit 24c59dc

File tree

3 files changed

+204
-8
lines changed

3 files changed

+204
-8
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
corev1 "k8s.io/api/core/v1"
1616
rbacv1 "k8s.io/api/rbac/v1"
1717
v1beta1ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
18+
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
1819
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1920
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021
"k8s.io/apimachinery/pkg/labels"
@@ -251,6 +252,21 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
251252

252253
}
253254

255+
// Register CustomResourceDefinition QueueInformer
256+
customResourceDefinitionInformer := extinf.NewSharedInformerFactory(op.OpClient.ApiextensionsV1beta1Interface(), wakeupInterval).Apiextensions().V1beta1().CustomResourceDefinitions()
257+
op.RegisterQueueInformer(queueinformer.NewInformer(
258+
workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "customresourcedefinitions"),
259+
customResourceDefinitionInformer.Informer(),
260+
op.syncObject,
261+
&cache.ResourceEventHandlerFuncs{
262+
DeleteFunc: op.handleDeletion,
263+
},
264+
"customresourcedefinitions",
265+
metrics.NewMetricsNil(),
266+
logger,
267+
))
268+
op.lister.APIExtensionsV1beta1().RegisterCustomResourceDefinitionLister(customResourceDefinitionInformer.Lister())
269+
254270
// Namespace sync for resolving subscriptions
255271
namespaceInformer := informers.NewSharedInformerFactory(op.opClient.KubernetesInterface(), resyncPeriod).Core().V1().Namespaces()
256272
op.lister.CoreV1().RegisterNamespaceLister(namespaceInformer.Lister())
@@ -1041,13 +1057,13 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
10411057
// Attempt to create the CRD.
10421058
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Create(&crd)
10431059
if k8serrors.IsAlreadyExists(err) {
1044-
currentCRD, _ := o.OpClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Get(crd.GetName(), metav1.GetOptions{})
1060+
currentCRD, _ := o.lister.APIExtensionsV1beta1().CustomResourceDefinitionLister().Get(crd.GetName())
10451061
// Compare 2 CRDs to see if it needs to be updatetd
10461062
if !reflect.DeepEqual(crd, *currentCRD) {
10471063
// Verify CRD ownership, only attempt to update if
10481064
// CRD has only one owner
10491065
// Example: provided=database.coreos.com/v1alpha1/EtcdCluster
1050-
matchedCSV, err := index.APIsIndexValues(o.csvProvidedAPIsIndexer, crd)
1066+
matchedCSV, err := index.CRDProviderNames(o.csvProvidedAPIsIndexer, crd)
10511067
if err != nil {
10521068
return errorwrap.Wrapf(err, "error find matched CSV: %s", step.Resource.Name)
10531069
}

pkg/lib/index/api.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const (
1414
ProvidedAPIsIndexFuncKey string = "providedAPIs"
1515
)
1616

17+
// ProvidedAPIsIndexFunc returns indicies from the owned CRDs and APIs of the given object (CSV)
1718
func ProvidedAPIsIndexFunc(obj interface{}) ([]string, error) {
1819
indicies := []string{}
1920

@@ -27,24 +28,24 @@ func ProvidedAPIsIndexFunc(obj interface{}) ([]string, error) {
2728
if len(parts) < 2 {
2829
return indicies, fmt.Errorf("couldn't parse plural.group from crd name: %s", crd.Name)
2930
}
30-
indicies = append(indicies, fmt.Sprintf("provided=%s/%s/%s", parts[1], crd.Version, crd.Kind))
31+
indicies = append(indicies, fmt.Sprintf("%s/%s/%s", parts[1], crd.Version, crd.Kind))
3132
}
3233
for _, api := range csv.Spec.APIServiceDefinitions.Owned {
33-
indicies = append(indicies, fmt.Sprintf("provided=%s/%s/%s", api.Group, api.Version, api.Kind))
34+
indicies = append(indicies, fmt.Sprintf("%s/%s/%s", api.Group, api.Version, api.Kind))
3435
}
3536

3637
return indicies, nil
3738
}
3839

39-
// APIsIndexValues returns the names of CSVs that own the given CRD
40-
func APIsIndexValues(indexers map[string]cache.Indexer, crd v1beta1ext.CustomResourceDefinition) (map[string]struct{}, error) {
40+
// CRDProviderNames returns the names of CSVs that own the given CRD
41+
func CRDProviderNames(indexers map[string]cache.Indexer, crd v1beta1ext.CustomResourceDefinition) (map[string]struct{}, error) {
4142
csvSet := map[string]struct{}{}
4243
crdSpec := map[string]struct{}{}
4344
for _, v := range crd.Spec.Versions {
44-
crdSpec[fmt.Sprintf("provided=%s/%s/%s", crd.Spec.Group, v.Name, crd.Spec.Names.Kind)] = struct{}{}
45+
crdSpec[fmt.Sprintf("%s/%s/%s", crd.Spec.Group, v.Name, crd.Spec.Names.Kind)] = struct{}{}
4546
}
4647
if crd.Spec.Version != "" {
47-
crdSpec[fmt.Sprintf("provided=%s/%s/%s", crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Kind)] = struct{}{}
48+
crdSpec[fmt.Sprintf("%s/%s/%s", crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Kind)] = struct{}{}
4849
}
4950
for _, indexer := range indexers {
5051
for key, _ := range crdSpec {

test/e2e/installplan_e2e_test.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
corev1 "k8s.io/api/core/v1"
1414
rbacv1 "k8s.io/api/rbac/v1"
1515
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
16+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
1617
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1718
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1819
"k8s.io/apimachinery/pkg/util/wait"
@@ -641,6 +642,184 @@ func TestCreateInstallPlanWithPreExistingCRDOwners(t *testing.T) {
641642
})
642643
}
643644

645+
func TestUpdateInstallPlan(t *testing.T) {
646+
defer cleaner.NotifyTestComplete(t, true)
647+
t.Run("UpdateSingleExistingCRDOwner", func(t *testing.T) {
648+
defer cleaner.NotifyTestComplete(t, true)
649+
650+
mainPackageName := genName("nginx-")
651+
652+
mainPackageStable := fmt.Sprintf("%s-stable", mainPackageName)
653+
654+
stableChannel := "stable"
655+
656+
mainNamedStrategy := newNginxInstallStrategy(genName("dep-"), nil, nil)
657+
658+
crdPlural := genName("ins-")
659+
crdName := crdPlural + ".cluster.com"
660+
mainCRD := apiextensions.CustomResourceDefinition{
661+
ObjectMeta: metav1.ObjectMeta{
662+
Name: crdName,
663+
},
664+
Spec: apiextensions.CustomResourceDefinitionSpec{
665+
Group: "cluster.com",
666+
Versions: []apiextensions.CustomResourceDefinitionVersion{
667+
{
668+
Name: "v1alpha1",
669+
Served: true,
670+
Storage: true,
671+
},
672+
},
673+
Names: apiextensions.CustomResourceDefinitionNames{
674+
Plural: crdPlural,
675+
Singular: crdPlural,
676+
Kind: crdPlural,
677+
ListKind: "list" + crdPlural,
678+
},
679+
Scope: "Namespaced",
680+
},
681+
}
682+
683+
updatedCRD := apiextensions.CustomResourceDefinition{
684+
ObjectMeta: metav1.ObjectMeta{
685+
Name: crdName,
686+
},
687+
Spec: apiextensions.CustomResourceDefinitionSpec{
688+
Group: "cluster.com",
689+
Versions: []apiextensions.CustomResourceDefinitionVersion{
690+
{
691+
Name: "v1alpha1",
692+
Served: true,
693+
Storage: true,
694+
},
695+
{
696+
Name: "v1alpha2",
697+
Served: true,
698+
Storage: false,
699+
},
700+
},
701+
Names: apiextensions.CustomResourceDefinitionNames{
702+
Plural: crdPlural,
703+
Singular: crdPlural,
704+
Kind: crdPlural,
705+
ListKind: "list" + crdPlural,
706+
},
707+
Scope: "Namespaced",
708+
},
709+
}
710+
711+
expectedCRDVersions := map[v1beta1.CustomResourceDefinitionVersion]struct{}{}
712+
for _, version := range updatedCRD.Spec.Versions {
713+
key := v1beta1.CustomResourceDefinitionVersion{
714+
Name: version.Name,
715+
Served: version.Served,
716+
Storage: version.Storage,
717+
}
718+
expectedCRDVersions[key] = struct{}{}
719+
}
720+
721+
mainCSV := newCSV(mainPackageStable, testNamespace, "", semver.MustParse("0.1.0"), []apiextensions.CustomResourceDefinition{mainCRD}, nil, mainNamedStrategy)
722+
723+
c := newKubeClient(t)
724+
crc := newCRClient(t)
725+
defer func() {
726+
require.NoError(t, crc.OperatorsV1alpha1().Subscriptions(testNamespace).DeleteCollection(&metav1.DeleteOptions{}, metav1.ListOptions{}))
727+
}()
728+
729+
mainCatalogName := genName("mock-ocs-main-")
730+
731+
// Create separate manifests for each CatalogSource
732+
mainManifests := []registry.PackageManifest{
733+
{
734+
PackageName: mainPackageName,
735+
Channels: []registry.PackageChannel{
736+
{Name: stableChannel, CurrentCSVName: mainPackageStable},
737+
},
738+
DefaultChannelName: stableChannel,
739+
},
740+
}
741+
742+
// Create the catalog sources
743+
_, cleanupMainCatalogSource := createInternalCatalogSource(t, c, crc, mainCatalogName, testNamespace, mainManifests, []apiextensions.CustomResourceDefinition{mainCRD}, []v1alpha1.ClusterServiceVersion{mainCSV})
744+
defer cleanupMainCatalogSource()
745+
// Attempt to get the catalog source before creating install plan
746+
_, err := fetchCatalogSource(t, crc, mainCatalogName, testNamespace, catalogSourceRegistryPodSynced)
747+
require.NoError(t, err)
748+
749+
subscriptionName := genName("sub-nginx-")
750+
subscriptionCleanup := createSubscriptionForCatalog(t, crc, testNamespace, subscriptionName, mainCatalogName, mainPackageName, stableChannel, "", v1alpha1.ApprovalAutomatic)
751+
defer subscriptionCleanup()
752+
753+
subscription, err := fetchSubscription(t, crc, testNamespace, subscriptionName, subscriptionHasInstallPlanChecker)
754+
require.NoError(t, err)
755+
require.NotNil(t, subscription)
756+
require.NotNil(t, subscription.Status.InstallPlanRef)
757+
require.Equal(t, mainCSV.GetName(), subscription.Status.CurrentCSV)
758+
759+
installPlanName := subscription.Status.InstallPlanRef.Name
760+
761+
// Wait for InstallPlan to be status: Complete before checking resource presence
762+
fetchedInstallPlan, err := fetchInstallPlan(t, crc, installPlanName, buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseComplete))
763+
require.NoError(t, err)
764+
765+
require.Equal(t, v1alpha1.InstallPlanPhaseComplete, fetchedInstallPlan.Status.Phase)
766+
767+
// Fetch installplan again to check for unnecessary control loops
768+
fetchedInstallPlan, err = fetchInstallPlan(t, crc, fetchedInstallPlan.GetName(), func(fip *v1alpha1.InstallPlan) bool {
769+
compareResources(t, fetchedInstallPlan, fip)
770+
return true
771+
})
772+
require.NoError(t, err)
773+
774+
// Verify CSV is created
775+
_, err = awaitCSV(t, crc, testNamespace, mainCSV.GetName(), csvAnyChecker)
776+
require.NoError(t, err)
777+
778+
// Create new CSV to replace the one CSV
779+
updatedCSV := newCSV(mainPackageStable+"-v2", testNamespace, mainPackageStable, semver.MustParse("0.1.1"), []apiextensions.CustomResourceDefinition{mainCRD}, nil, mainNamedStrategy)
780+
781+
// Update manifest
782+
updatedManifests := []registry.PackageManifest{
783+
{
784+
PackageName: mainPackageName,
785+
Channels: []registry.PackageChannel{
786+
{Name: stableChannel, CurrentCSVName: updatedCSV.GetName()},
787+
},
788+
DefaultChannelName: stableChannel,
789+
},
790+
}
791+
792+
updateInternalCatalog(t, c, crc, mainCatalogName, testNamespace, []apiextensions.CustomResourceDefinition{updatedCRD}, []v1alpha1.ClusterServiceVersion{mainCSV, updatedCSV}, updatedManifests)
793+
794+
// Wait for subscription to update
795+
updatedSubscription, err := fetchSubscription(t, crc, testNamespace, subscriptionName, subscriptionHasCurrentCSV(updatedCSV.GetName()))
796+
require.NoError(t, err)
797+
798+
// Verify installplan created and installed
799+
fetchedUpdatedInstallPlan, err := fetchInstallPlan(t, crc, updatedSubscription.Status.InstallPlanRef.Name, buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseComplete))
800+
require.NoError(t, err)
801+
require.NotEqual(t, fetchedInstallPlan.GetName(), fetchedUpdatedInstallPlan.GetName())
802+
803+
// Wait for csv to update
804+
_, err = awaitCSV(t, crc, testNamespace, updatedCSV.GetName(), csvAnyChecker)
805+
require.NoError(t, err)
806+
807+
// Get the CRD to see if it is updated
808+
fetchedCRD, err := c.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Get(crdName, metav1.GetOptions{})
809+
require.NoError(t, err)
810+
811+
for _, version := range fetchedCRD.Spec.Versions {
812+
key := v1beta1.CustomResourceDefinitionVersion{
813+
Name: version.Name,
814+
Served: version.Served,
815+
Storage: version.Storage,
816+
}
817+
_, ok := expectedCRDVersions[key]
818+
require.True(t, ok, "couldn't find %v in expected CRD versions: %#v", key, expectedCRDVersions)
819+
}
820+
})
821+
}
822+
644823
// TestCreateInstallPlanWithPermissions creates an InstallPlan with a CSV containing a set of permissions to be resolved.
645824
func TestCreateInstallPlanWithPermissions(t *testing.T) {
646825
defer cleaner.NotifyTestComplete(t, true)

0 commit comments

Comments
 (0)