Skip to content

Commit 7697393

Browse files
committed
test(installplan): add cr ownerref unit testcase
Add a unit testcase to ensure custom resources created by InstallPlan execution are ownerrefed to the accompanying CSV only.
1 parent 42df653 commit 7697393

File tree

3 files changed

+242
-22
lines changed

3 files changed

+242
-22
lines changed

pkg/controller/operators/catalog/operator_test.go

Lines changed: 204 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,34 @@ import (
66
"errors"
77
"fmt"
88
"io/ioutil"
9+
"strings"
910
"testing"
1011
"time"
1112

12-
"github.com/ghodss/yaml"
1313
"github.com/sirupsen/logrus"
1414
"github.com/stretchr/testify/require"
1515
"golang.org/x/time/rate"
16+
"gopkg.in/yaml.v2"
1617
appsv1 "k8s.io/api/apps/v1"
1718
corev1 "k8s.io/api/core/v1"
1819
rbacv1 "k8s.io/api/rbac/v1"
1920
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
2021
apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
22+
"k8s.io/apimachinery/pkg/api/meta"
2123
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2225
"k8s.io/apimachinery/pkg/runtime"
26+
"k8s.io/apimachinery/pkg/runtime/schema"
2327
"k8s.io/apimachinery/pkg/types"
2428
utilclock "k8s.io/apimachinery/pkg/util/clock"
29+
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
2530
fakedynamic "k8s.io/client-go/dynamic/fake"
2631
"k8s.io/client-go/informers"
2732
k8sfake "k8s.io/client-go/kubernetes/fake"
2833
"k8s.io/client-go/rest"
2934
"k8s.io/client-go/tools/cache"
3035
"k8s.io/client-go/util/workqueue"
36+
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
3137
apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
3238

3339
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
@@ -342,6 +348,7 @@ func TestExecutePlan(t *testing.T) {
342348
tests := []struct {
343349
testName string
344350
in *v1alpha1.InstallPlan
351+
extObjs []runtime.Object
345352
want []runtime.Object
346353
err error
347354
}{
@@ -363,7 +370,7 @@ func TestExecutePlan(t *testing.T) {
363370
Version: "v1",
364371
Kind: "Service",
365372
Name: "service",
366-
Manifest: toManifest(service("service", namespace)),
373+
Manifest: toManifest(t, service("service", namespace)),
367374
},
368375
Status: v1alpha1.StepStatusUnknown,
369376
},
@@ -375,7 +382,7 @@ func TestExecutePlan(t *testing.T) {
375382
Version: "v1alpha1",
376383
Kind: "ClusterServiceVersion",
377384
Name: "csv",
378-
Manifest: toManifest(csv("csv", namespace, nil, nil)),
385+
Manifest: toManifest(t, csv("csv", namespace, nil, nil)),
379386
},
380387
Status: v1alpha1.StepStatusUnknown,
381388
},
@@ -396,7 +403,7 @@ func TestExecutePlan(t *testing.T) {
396403
Version: "v1",
397404
Kind: "ServiceAccount",
398405
Name: "sa",
399-
Manifest: toManifest(serviceAccount("sa", namespace, "",
406+
Manifest: toManifest(t, serviceAccount("sa", namespace, "",
400407
objectReference("init secret"))),
401408
},
402409
Status: v1alpha1.StepStatusUnknown,
@@ -418,7 +425,7 @@ func TestExecutePlan(t *testing.T) {
418425
Version: "v1",
419426
Kind: "ServiceAccount",
420427
Name: "sa",
421-
Manifest: toManifest(serviceAccount("sa", namespace, "name",
428+
Manifest: toManifest(t, serviceAccount("sa", namespace, "name",
422429
objectReference("init secret"))),
423430
},
424431
Status: v1alpha1.StepStatusUnknown,
@@ -431,7 +438,7 @@ func TestExecutePlan(t *testing.T) {
431438
Version: "v1",
432439
Kind: "ServiceAccount",
433440
Name: "sa",
434-
Manifest: toManifest(serviceAccount("sa", namespace, "name", nil)),
441+
Manifest: toManifest(t, serviceAccount("sa", namespace, "name", nil)),
435442
},
436443
Status: v1alpha1.StepStatusUnknown,
437444
},
@@ -452,7 +459,7 @@ func TestExecutePlan(t *testing.T) {
452459
Version: "v1",
453460
Kind: "ServiceAccount",
454461
Name: "sa",
455-
Manifest: toManifest(serviceAccount("sa", namespace, "old_name",
462+
Manifest: toManifest(t, serviceAccount("sa", namespace, "old_name",
456463
objectReference("init secret"))),
457464
},
458465
Status: v1alpha1.StepStatusUnknown,
@@ -465,7 +472,7 @@ func TestExecutePlan(t *testing.T) {
465472
Version: "v1",
466473
Kind: "ServiceAccount",
467474
Name: "sa",
468-
Manifest: toManifest(serviceAccount("sa", namespace, "new_name", nil)),
475+
Manifest: toManifest(t, serviceAccount("sa", namespace, "new_name", nil)),
469476
},
470477
Status: v1alpha1.StepStatusUnknown,
471478
},
@@ -474,19 +481,62 @@ func TestExecutePlan(t *testing.T) {
474481
want: []runtime.Object{serviceAccount("sa", namespace, "new_name", objectReference("init secret"))},
475482
err: nil,
476483
},
484+
{
485+
testName: "DynamicResourcesAreOwnerReferencedToCSV",
486+
in: withSteps(installPlan("p", namespace, v1alpha1.InstallPlanPhaseInstalling, "csv"),
487+
[]*v1alpha1.Step{
488+
{
489+
Resolving: "csv",
490+
Resource: v1alpha1.StepResource{
491+
CatalogSource: "catalog",
492+
CatalogSourceNamespace: namespace,
493+
Group: "operators.coreos.com",
494+
Version: "v1alpha1",
495+
Kind: "ClusterServiceVersion",
496+
Name: "csv",
497+
Manifest: toManifest(t, csv("csv", namespace, nil, nil)),
498+
},
499+
Status: v1alpha1.StepStatusUnknown,
500+
},
501+
{
502+
Resolving: "csv",
503+
Resource: v1alpha1.StepResource{
504+
CatalogSource: "catalog",
505+
CatalogSourceNamespace: namespace,
506+
Group: "monitoring.coreos.com",
507+
Version: "v1",
508+
Kind: "PrometheusRule",
509+
Name: "rule",
510+
Manifest: toManifest(t, decodeFile(t, "./testdata/prometheusrule.cr.yaml", &unstructured.Unstructured{})),
511+
},
512+
Status: v1alpha1.StepStatusUnknown,
513+
},
514+
},
515+
),
516+
extObjs: []runtime.Object{decodeFile(t, "./testdata/prometheusrule.crd.yaml", &v1beta1.CustomResourceDefinition{})},
517+
want: []runtime.Object{
518+
csv("csv", namespace, nil, nil),
519+
modify(t, decodeFile(t, "./testdata/prometheusrule.cr.yaml", &unstructured.Unstructured{}),
520+
withNamespace(namespace),
521+
withOwner(csv("csv", namespace, nil, nil)),
522+
),
523+
},
524+
err: nil,
525+
},
477526
}
478527

479528
for _, tt := range tests {
480529
t.Run(tt.testName, func(t *testing.T) {
481530
ctx, cancel := context.WithCancel(context.TODO())
482531
defer cancel()
483532

484-
op, err := NewFakeOperator(ctx, namespace, []string{namespace}, withClientObjs(tt.in))
533+
op, err := NewFakeOperator(ctx, namespace, []string{namespace}, withClientObjs(tt.in), withExtObjs(tt.extObjs...))
485534
require.NoError(t, err)
486535

487536
err = op.ExecutePlan(tt.in)
488537
require.Equal(t, tt.err, err)
489538

539+
getOpts := metav1.GetOptions{}
490540
for _, obj := range tt.want {
491541
var err error
492542
var fetched runtime.Object
@@ -507,8 +557,29 @@ func TestExecutePlan(t *testing.T) {
507557
fetched, err = op.opClient.GetSecret(namespace, o.GetName())
508558
case *corev1.Service:
509559
fetched, err = op.opClient.GetService(namespace, o.GetName())
560+
case *v1beta1.CustomResourceDefinition:
561+
fetched, err = op.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Get(o.GetName(), getOpts)
510562
case *v1alpha1.ClusterServiceVersion:
511-
fetched, err = op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(o.GetName(), metav1.GetOptions{})
563+
fetched, err = op.client.OperatorsV1alpha1().ClusterServiceVersions(namespace).Get(o.GetName(), getOpts)
564+
case *unstructured.Unstructured:
565+
// Get the resource from the GVK
566+
gvk := o.GroupVersionKind()
567+
var r metav1.APIResource
568+
r, err = op.apiresourceFromGVK(gvk)
569+
require.NoError(t, err)
570+
571+
gvr := schema.GroupVersionResource{
572+
Group: gvk.Group,
573+
Version: gvk.Version,
574+
Resource: r.Name,
575+
}
576+
577+
if r.Namespaced {
578+
fetched, err = op.dynamicClient.Resource(gvr).Namespace(namespace).Get(o.GetName(), getOpts)
579+
break
580+
}
581+
582+
fetched, err = op.dynamicClient.Resource(gvr).Get(o.GetName(), getOpts)
512583
default:
513584
require.Failf(t, "couldn't find expected object", "%#v", obj)
514585
}
@@ -558,8 +629,7 @@ func TestSupportedDynamicResources(t *testing.T) {
558629

559630
func TestExecutePlanDynamicResources(t *testing.T) {
560631
namespace := "ns"
561-
unsupportedYaml, err := yamlFromFilePath("testdata/unsupportedkind.cr.yaml")
562-
require.NoError(t, err)
632+
unsupportedYaml := yamlFromFilePath(t, "testdata/unsupportedkind.cr.yaml")
563633

564634
tests := []struct {
565635
testName string
@@ -593,7 +663,7 @@ func TestExecutePlanDynamicResources(t *testing.T) {
593663
ctx, cancel := context.WithCancel(context.TODO())
594664
defer cancel()
595665

596-
op, err := NewFakeOperator(ctx, namespace, []string{namespace})
666+
op, err := NewFakeOperator(ctx, namespace, []string{namespace}, withClientObjs(tt.in))
597667
require.NoError(t, err)
598668

599669
err = op.ExecutePlan(tt.in)
@@ -991,7 +1061,7 @@ func withK8sObjs(k8sObjs ...runtime.Object) fakeOperatorOption {
9911061
}
9921062
}
9931063

994-
func extObjs(extObjs ...runtime.Object) fakeOperatorOption {
1064+
func withExtObjs(extObjs ...runtime.Object) fakeOperatorOption {
9951065
return func(config *fakeOperatorConfig) {
9961066
config.extObjs = extObjs
9971067
}
@@ -1017,8 +1087,13 @@ func NewFakeOperator(ctx context.Context, namespace string, namespaces []string,
10171087

10181088
// Create client fakes
10191089
clientFake := fake.NewReactionForwardingClientsetDecorator(config.clientObjs, config.clientOptions...)
1020-
opClientFake := operatorclient.NewClient(k8sfake.NewSimpleClientset(config.k8sObjs...), apiextensionsfake.NewSimpleClientset(config.extObjs...), apiregistrationfake.NewSimpleClientset(config.regObjs...))
1090+
// TODO: Using the ReactionForwardingClientsetDecorator for k8s objects causes issues with adding Resources for discovery.
1091+
// For now, directly use a SimpleClientset instead.
1092+
k8sClientFake := k8sfake.NewSimpleClientset(config.k8sObjs...)
1093+
k8sClientFake.Resources = apiResourcesForObjects(append(config.extObjs, config.regObjs...))
1094+
opClientFake := operatorclient.NewClient(k8sClientFake, apiextensionsfake.NewSimpleClientset(config.extObjs...), apiregistrationfake.NewSimpleClientset(config.regObjs...))
10211095
dynamicClientFake := fakedynamic.NewSimpleDynamicClient(runtime.NewScheme())
1096+
// dynamicClientFake.Resources = k8sClientFake.Resources
10221097

10231098
// Create operator namespace
10241099
_, err := opClientFake.KubernetesInterface().CoreV1().Namespaces().Create(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}})
@@ -1205,17 +1280,17 @@ func objectReference(name string) *corev1.ObjectReference {
12051280
return &corev1.ObjectReference{Name: name}
12061281
}
12071282

1208-
func yamlFromFilePath(fileName string) (string, error) {
1283+
func yamlFromFilePath(t *testing.T, fileName string) string {
12091284
yaml, err := ioutil.ReadFile(fileName)
1210-
if err != nil {
1211-
return "", err
1212-
}
1285+
require.NoError(t, err)
12131286

1214-
return string(yaml), nil
1287+
return string(yaml)
12151288
}
12161289

1217-
func toManifest(obj runtime.Object) string {
1218-
raw, _ := json.Marshal(obj)
1290+
func toManifest(t *testing.T, obj runtime.Object) string {
1291+
raw, err := json.Marshal(obj)
1292+
require.NoError(t, err)
1293+
12191294
return string(raw)
12201295
}
12211296

@@ -1224,3 +1299,110 @@ func pod(s v1alpha1.CatalogSource) *corev1.Pod {
12241299
ownerutil.AddOwner(pod, &s, false, false)
12251300
return pod
12261301
}
1302+
1303+
func decodeFile(t *testing.T, file string, to runtime.Object) runtime.Object {
1304+
manifest := yamlFromFilePath(t, file)
1305+
dec := utilyaml.NewYAMLOrJSONDecoder(strings.NewReader(manifest), 10)
1306+
require.NoError(t, dec.Decode(to))
1307+
1308+
return to
1309+
}
1310+
1311+
type modifierFunc func(t *testing.T, obj runtime.Object) runtime.Object
1312+
1313+
func modify(t *testing.T, obj runtime.Object, modifiers ...modifierFunc) runtime.Object {
1314+
o := obj.DeepCopyObject()
1315+
for _, modifier := range modifiers {
1316+
o = modifier(t, o)
1317+
}
1318+
1319+
return o
1320+
}
1321+
1322+
type metaModifierFunc func(m metav1.Object)
1323+
1324+
func modifyMeta(mf metaModifierFunc) modifierFunc {
1325+
return func(t *testing.T, obj runtime.Object) runtime.Object {
1326+
accessor, err := meta.Accessor(obj)
1327+
require.NoError(t, err)
1328+
1329+
mf(accessor)
1330+
1331+
return obj
1332+
}
1333+
}
1334+
1335+
func withNamespace(namespace string) modifierFunc {
1336+
return modifyMeta(func(m metav1.Object) {
1337+
m.SetNamespace(namespace)
1338+
})
1339+
}
1340+
1341+
func withOwner(owner ownerutil.Owner) modifierFunc {
1342+
return modifyMeta(func(m metav1.Object) {
1343+
ownerutil.AddNonBlockingOwner(m, owner)
1344+
})
1345+
}
1346+
1347+
func withObjectMeta(t *testing.T, obj runtime.Object, m *metav1.ObjectMeta) runtime.Object {
1348+
o := obj.DeepCopyObject()
1349+
accessor, err := meta.Accessor(o)
1350+
require.NoError(t, err)
1351+
1352+
accessor.SetAnnotations(m.GetAnnotations())
1353+
accessor.SetClusterName(m.GetClusterName())
1354+
accessor.SetCreationTimestamp(m.GetCreationTimestamp())
1355+
accessor.SetDeletionGracePeriodSeconds(m.GetDeletionGracePeriodSeconds())
1356+
accessor.SetDeletionTimestamp(m.GetDeletionTimestamp())
1357+
accessor.SetFinalizers(m.GetFinalizers())
1358+
accessor.SetGenerateName(m.GetGenerateName())
1359+
accessor.SetGeneration(m.GetGeneration())
1360+
accessor.SetLabels(m.GetLabels())
1361+
accessor.SetManagedFields(m.GetManagedFields())
1362+
accessor.SetName(m.GetName())
1363+
accessor.SetNamespace(m.GetNamespace())
1364+
accessor.SetOwnerReferences(m.GetOwnerReferences())
1365+
accessor.SetResourceVersion(m.GetResourceVersion())
1366+
accessor.SetSelfLink(m.GetSelfLink())
1367+
accessor.SetUID(m.GetUID())
1368+
1369+
return o
1370+
}
1371+
1372+
func apiResourcesForObjects(objs []runtime.Object) []*metav1.APIResourceList {
1373+
apis := []*metav1.APIResourceList{}
1374+
for _, o := range objs {
1375+
switch o.(type) {
1376+
case *v1beta1.CustomResourceDefinition:
1377+
crd := o.(*v1beta1.CustomResourceDefinition)
1378+
apis = append(apis, &metav1.APIResourceList{
1379+
GroupVersion: metav1.GroupVersion{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name}.String(),
1380+
APIResources: []metav1.APIResource{
1381+
{
1382+
Name: crd.GetName(),
1383+
SingularName: crd.Spec.Names.Singular,
1384+
Namespaced: crd.Spec.Scope == v1beta1.NamespaceScoped,
1385+
Group: crd.Spec.Group,
1386+
Version: crd.Spec.Versions[0].Name,
1387+
Kind: crd.Spec.Names.Kind,
1388+
},
1389+
},
1390+
})
1391+
case *apiregistrationv1.APIService:
1392+
a := o.(*apiregistrationv1.APIService)
1393+
names := strings.Split(a.Name, ".")
1394+
apis = append(apis, &metav1.APIResourceList{
1395+
GroupVersion: metav1.GroupVersion{Group: names[1], Version: a.Spec.Version}.String(),
1396+
APIResources: []metav1.APIResource{
1397+
{
1398+
Name: names[1],
1399+
Group: names[1],
1400+
Version: a.Spec.Version,
1401+
Kind: names[1] + "Kind",
1402+
},
1403+
},
1404+
})
1405+
}
1406+
}
1407+
return apis
1408+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: monitoring.coreos.com/v1
2+
kind: PrometheusRule
3+
metadata:
4+
name: rule
5+
# namespace: placeholder
6+
labels:
7+
prometheus: alert-rules
8+
role: alert-rules
9+
spec:
10+
groups:
11+
- name: olm.failing_operators.rules
12+
rules:
13+
- alert: SeriousAlert
14+
annotations:
15+
message: A serious alert!
16+
expr: alert_status{prioirity="Serious"}
17+
labels:
18+
severity: warn

0 commit comments

Comments
 (0)