Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 214 additions & 2 deletions test/experimental-e2e/experimental_e2e_test.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
package experimental_e2e

import (
"context"
"fmt"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

ocv1 "github.com/operator-framework/operator-controller/api/v1"
"github.com/operator-framework/operator-controller/internal/operator-controller/scheme"
"github.com/operator-framework/operator-controller/test/utils"
)

const (
artifactName = "operator-controller-experimental-e2e"
pollDuration = time.Minute
pollInterval = time.Second
)

var (
cfg *rest.Config
c client.Client
cfg *rest.Config
c client.Client
dynamicClient dynamic.Interface
)

func TestMain(m *testing.M) {
Expand All @@ -31,10 +50,203 @@ func TestMain(m *testing.M) {
c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
utilruntime.Must(err)

dynamicClient, err = dynamic.NewForConfig(cfg)
utilruntime.Must(err)

os.Exit(m.Run())
}

func TestNoop(t *testing.T) {
t.Log("Running experimental-e2e tests")
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)
}

func TestWebhookSupport(t *testing.T) {
t.Log("Test support for bundles with webhooks")
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)

t.Log("By creating install namespace, and necessary rbac resources")
namespace := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "webhook-operator",
},
}
require.NoError(t, c.Create(t.Context(), &namespace))
t.Cleanup(func() {
require.NoError(t, c.Delete(context.Background(), &namespace))
})

serviceAccount := corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "webhook-operator-installer",
Namespace: namespace.GetName(),
},
}
require.NoError(t, c.Create(t.Context(), &serviceAccount))
t.Cleanup(func() {
require.NoError(t, c.Delete(context.Background(), &serviceAccount))
})

clusterRoleBinding := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "webhook-operator-installer",
},
Subjects: []rbacv1.Subject{
{
Kind: "ServiceAccount",
APIGroup: corev1.GroupName,
Name: serviceAccount.GetName(),
Namespace: serviceAccount.GetNamespace(),
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: "cluster-admin",
},
}
require.NoError(t, c.Create(t.Context(), clusterRoleBinding))
t.Cleanup(func() {
require.NoError(t, c.Delete(context.Background(), clusterRoleBinding))
})

t.Log("By creating the webhook-operator ClusterCatalog")
extensionCatalog := &ocv1.ClusterCatalog{
ObjectMeta: metav1.ObjectMeta{
Name: "webhook-operator-catalog",
},
Spec: ocv1.ClusterCatalogSpec{
Source: ocv1.CatalogSource{
Type: ocv1.SourceTypeImage,
Image: &ocv1.ImageSource{
Ref: fmt.Sprintf("%s/e2e/test-catalog:v1", os.Getenv("LOCAL_REGISTRY_HOST")),
PollIntervalMinutes: ptr.To(1),
},
},
},
}
require.NoError(t, c.Create(t.Context(), extensionCatalog))
t.Cleanup(func() {
require.NoError(t, c.Delete(context.Background(), extensionCatalog))
})

t.Log("By waiting for the catalog to serve its metadata")
require.EventuallyWithT(t, func(ct *assert.CollectT) {
assert.NoError(t, c.Get(context.Background(), types.NamespacedName{Name: extensionCatalog.GetName()}, extensionCatalog))
cond := apimeta.FindStatusCondition(extensionCatalog.Status.Conditions, ocv1.TypeServing)
assert.NotNil(t, cond)
assert.Equal(t, metav1.ConditionTrue, cond.Status)
assert.Equal(t, ocv1.ReasonAvailable, cond.Reason)
}, pollDuration, pollInterval)

t.Log("By installing the webhook-operator ClusterExtension")
clusterExtension := &ocv1.ClusterExtension{
ObjectMeta: metav1.ObjectMeta{
Name: "webhook-operator-extension",
},
Spec: ocv1.ClusterExtensionSpec{
Source: ocv1.SourceConfig{
SourceType: "Catalog",
Catalog: &ocv1.CatalogFilter{
PackageName: "webhook-operator",
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"olm.operatorframework.io/metadata.name": extensionCatalog.Name},
},
},
},
Namespace: namespace.GetName(),
ServiceAccount: ocv1.ServiceAccountReference{
Name: serviceAccount.GetName(),
},
},
}
require.NoError(t, c.Create(t.Context(), clusterExtension))
t.Cleanup(func() {
require.NoError(t, c.Delete(context.Background(), clusterExtension))
})

t.Log("By waiting for webhook-operator extension to be installed successfully")
require.EventuallyWithT(t, func(ct *assert.CollectT) {
assert.NoError(ct, c.Get(t.Context(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1.TypeInstalled)
if assert.NotNil(ct, cond) {
assert.Equal(ct, metav1.ConditionTrue, cond.Status)
assert.Equal(ct, ocv1.ReasonSucceeded, cond.Reason)
assert.Contains(ct, cond.Message, "Installed bundle")
assert.NotEmpty(ct, clusterExtension.Status.Install.Bundle)
}
}, pollDuration, pollInterval)

t.Log("By waiting for webhook-operator deployment to be available")
require.EventuallyWithT(t, func(ct *assert.CollectT) {
deployment := &appsv1.Deployment{}
assert.NoError(ct, c.Get(t.Context(), types.NamespacedName{Namespace: namespace.GetName(), Name: "webhook-operator-webhook"}, deployment))
available := false
for _, cond := range deployment.Status.Conditions {
if cond.Type == appsv1.DeploymentAvailable {
available = cond.Status == corev1.ConditionTrue
}
}
assert.True(ct, available)
}, pollDuration, pollInterval)

v1Gvr := schema.GroupVersionResource{
Group: "webhook.operators.coreos.io",
Version: "v1",
Resource: "webhooktests",
}
v1Client := dynamicClient.Resource(v1Gvr).Namespace(namespace.GetName())

t.Log("By checking an invalid CR is rejected by the validating webhook")
obj := getWebhookOperatorResource("invalid-test-cr", namespace.GetName(), false)
_, err := v1Client.Create(t.Context(), obj, metav1.CreateOptions{})
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid value: false: Spec.Valid must be true")

t.Log("By checking a valid CR is mutated by the mutating webhook")
obj = getWebhookOperatorResource("valid-test-cr", namespace.GetName(), true)
_, err = v1Client.Create(t.Context(), obj, metav1.CreateOptions{})
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, dynamicClient.Resource(v1Gvr).Namespace(namespace.GetName()).Delete(context.Background(), obj.GetName(), metav1.DeleteOptions{}))
})
res, err := v1Client.Get(t.Context(), obj.GetName(), metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, map[string]interface{}{
"valid": true,
"mutate": true,
}, res.Object["spec"])

t.Log("By checking a valid CR is converted to v2 by the conversion webhook")
v2Gvr := schema.GroupVersionResource{
Group: "webhook.operators.coreos.io",
Version: "v2",
Resource: "webhooktests",
}
v2Client := dynamicClient.Resource(v2Gvr).Namespace(namespace.GetName())

res, err = v2Client.Get(t.Context(), obj.GetName(), metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, map[string]interface{}{
"conversion": map[string]interface{}{
"valid": true,
"mutate": true,
},
}, res.Object["spec"])
}

func getWebhookOperatorResource(name string, namespace string, valid bool) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "webhook.operators.coreos.io/v1",
"kind": "webhooktests",
"metadata": map[string]interface{}{
"name": name,
"namespace": namespace,
},
"spec": map[string]interface{}{
"valid": valid,
},
},
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: webhook-operator-metrics-reader
rules:
- nonResourceURLs:
- /metrics
verbs:
- get
Loading
Loading