Skip to content

Commit

Permalink
e2e/webhooks: clean up webhook fixture and merge zero call check
Browse files Browse the repository at this point in the history
  • Loading branch information
sttts authored and Shawn Hurley committed Apr 29, 2022
1 parent edf8953 commit 096d495
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 284 deletions.
320 changes: 95 additions & 225 deletions test/e2e/apibinding/apibinding_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ package apibinding

import (
"context"
"sync"
"path/filepath"
"testing"
"time"

"github.com/kcp-dev/apimachinery/pkg/logicalcluster"
"github.com/stretchr/testify/require"

v1 "k8s.io/api/admission/v1"
Expand Down Expand Up @@ -117,52 +118,53 @@ func TestAPIBindingMutatingWebhook(t *testing.T) {
codecs := serializer.NewCodecFactory(scheme)
deserializer := codecs.UniversalDeserializer()

//Create Test Server and Create Validating Webhook for Cowboys in source cluster.
testWebhook := webhookserver.WebhookServer{
Response: v1.AdmissionResponse{
Allowed: true,
},
ObjectGVK: schema.GroupVersionKind{
Group: "wildwest.dev",
Version: "v1alpha1",
Kind: "Cowboy",
},
T: t,
Lock: sync.Mutex{},
Deserializer: deserializer,
}

port, err := framework.GetFreePort(t)
require.NoError(t, err, "failed to get free port for test webhook")
testWebhook.StartServer(ctx, server, port)

t.Logf("Installing webhook into the source workspace")
sideEffect := admissionregistrationv1.SideEffectClassNone
webhook := &admissionregistrationv1.MutatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{Name: "test-webhook"},
Webhooks: []admissionregistrationv1.MutatingWebhook{{
Name: "test-webhook.cowboy.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: testWebhook.GetURL(),
CABundle: cfg.CAData,
t.Logf("Create test server and create mutating webhook for cowboys in both source and target cluster")
testWebhooks := map[logicalcluster.LogicalCluster]*webhookserver.AdmissionWebhookServer{}
for _, cluster := range []logicalcluster.LogicalCluster{sourceWorkspace, targetWorkspace} {
testWebhooks[cluster] = &webhookserver.AdmissionWebhookServer{
Response: v1.AdmissionResponse{
Allowed: true,
},
Rules: []admissionregistrationv1.RuleWithOperations{{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"wildwest.dev"},
APIVersions: []string{"v1alpha1"},
Resources: []string{"cowboys"},
ObjectGVK: schema.GroupVersionKind{
Group: "wildwest.dev",
Version: "v1alpha1",
Kind: "Cowboy",
},
Deserializer: deserializer,
}
port, err := framework.GetFreePort(t)
require.NoError(t, err, "failed to get free port for test webhook")
dirPath := filepath.Dir(server.KubeconfigPath())
testWebhooks[cluster].StartTLS(t, filepath.Join(dirPath, "apiserver.crt"), filepath.Join(dirPath, "apiserver.key"), port)

sideEffect := admissionregistrationv1.SideEffectClassNone
url := testWebhooks[cluster].GetURL()
webhook := &admissionregistrationv1.MutatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{Name: "test-webhook"},
Webhooks: []admissionregistrationv1.MutatingWebhook{{
Name: "test-webhook.cowboy.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: &url,
CABundle: cfg.CAData,
},
Rules: []admissionregistrationv1.RuleWithOperations{{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"wildwest.dev"},
APIVersions: []string{"v1alpha1"},
Resources: []string{"cowboys"},
},
}},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1"},
}},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1"},
}},
}
_, err = kubeClusterClient.Cluster(cluster).AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, webhook, metav1.CreateOptions{})
require.NoError(t, err, "failed to add validating webhook configurations")
}
_, err = kubeClusterClient.Cluster(sourceWorkspace).AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, webhook, metav1.CreateOptions{})
require.NoError(t, err, "failed to add validating webhook configurations")

cowboy := v1alpha1.Cowboy{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -178,9 +180,11 @@ func TestAPIBindingMutatingWebhook(t *testing.T) {
if err != nil && !errors.IsAlreadyExists(err) {
return false
}
return testWebhook.Calls >= 1

return testWebhooks[sourceWorkspace].Calls() >= 1
}, wait.ForeverTestTimeout, 100*time.Millisecond)

t.Logf("Check that the in-workspace webhook was NOT called")
require.Zero(t, testWebhooks[targetWorkspace].Calls(), "in-workspace webhook should not have been called")
}

func TestAPIBindingValidatingWebhook(t *testing.T) {
Expand Down Expand Up @@ -254,189 +258,53 @@ func TestAPIBindingValidatingWebhook(t *testing.T) {
codecs := serializer.NewCodecFactory(scheme)
deserializer := codecs.UniversalDeserializer()

//Create Test Server and Create Validating Webhook for Cowboys in source cluster.
testWebhook := webhookserver.WebhookServer{
Response: v1.AdmissionResponse{
Allowed: true,
},
ObjectGVK: schema.GroupVersionKind{
Group: "wildwest.dev",
Version: "v1alpha1",
Kind: "Cowboy",
},
T: t,
Lock: sync.Mutex{},
Deserializer: deserializer,
}

port, err := framework.GetFreePort(t)
require.NoError(t, err, "failed to get free port for test webhook")
testWebhook.StartServer(ctx, server, port)

t.Logf("Installing webhook into the source workspace")
sideEffect := admissionregistrationv1.SideEffectClassNone
webhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{Name: "test-webhook"},
Webhooks: []admissionregistrationv1.ValidatingWebhook{{
Name: "test-webhook.cowboy.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: testWebhook.GetURL(),
CABundle: cfg.CAData,
},
Rules: []admissionregistrationv1.RuleWithOperations{{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"wildwest.dev"},
APIVersions: []string{"v1alpha1"},
Resources: []string{"cowboys"},
},
}},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1"},
}},
}
_, err = kubeClusterClient.Cluster(sourceWorkspace).AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, webhook, metav1.CreateOptions{})
require.NoError(t, err, "failed to add validating webhook configurations")

cowboy := v1alpha1.Cowboy{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "testing",
},
Spec: v1alpha1.CowboySpec{},
}

// Avoid race condition here by making sure that CRD is served after installing the types into logical clusters
t.Logf("Creating cowboy resource in target logical cluster")
require.Eventually(t, func() bool {
_, err = cowbyClients.Cluster(targetWorkspace).WildwestV1alpha1().Cowboys("default").Create(ctx, &cowboy, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
return false
}
return testWebhook.Calls >= 1

}, wait.ForeverTestTimeout, 100*time.Millisecond)
}

func TestAPIBindingValidatingWebhookNotCalledWhenBound(t *testing.T) {
t.Parallel()

server := framework.SharedKcpServer(t)

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

orgClusterName := framework.NewOrganizationFixture(t, server)
sourceWorkspace := framework.NewWorkspaceFixture(t, server, orgClusterName, "Universal")
targetWorkspace := framework.NewWorkspaceFixture(t, server, orgClusterName, "Universal")

cfg := server.DefaultConfig(t)

kcpClients, err := clientset.NewClusterForConfig(cfg)
require.NoError(t, err, "failed to construct kcp cluster client for server")

dynamicClients, err := dynamic.NewClusterForConfig(cfg)
require.NoError(t, err, "failed to construct dynamic cluster client for server")

kubeClusterClient, err := kubernetes.NewClusterForConfig(cfg)
require.NoError(t, err, "failed to construct client for server")

t.Logf("Install a cowboys APIResourceSchema into workspace %q", sourceWorkspace)
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(kcpClients.Cluster(sourceWorkspace).Discovery()))
err = helpers.CreateResourceFromFS(ctx, dynamicClients.Cluster(sourceWorkspace), mapper, "apiresourceschema_cowboys.yaml", testFiles)
require.NoError(t, err)

t.Logf("Create an APIExport for it")
cowboysAPIExport := &apisv1alpha1.APIExport{
ObjectMeta: metav1.ObjectMeta{
Name: "today-cowboys",
},
Spec: apisv1alpha1.APIExportSpec{
LatestResourceSchemas: []string{"today.cowboys.wildwest.dev"},
},
}
_, err = kcpClients.Cluster(sourceWorkspace).ApisV1alpha1().APIExports().Create(ctx, cowboysAPIExport, metav1.CreateOptions{})
require.NoError(t, err)

t.Logf("Create an APIBinding in workspace %q that points to the today-cowboys export", targetWorkspace)
require.NoError(t, err)
apiBinding := &apisv1alpha1.APIBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "cowboys",
},
Spec: apisv1alpha1.APIBindingSpec{
Reference: apisv1alpha1.ExportReference{
Workspace: &apisv1alpha1.WorkspaceExportReference{
WorkspaceName: sourceWorkspace.Base(),
ExportName: cowboysAPIExport.Name,
},
t.Logf("Create test server and create validating webhook for cowboys in both source and target cluster")
testWebhooks := map[logicalcluster.LogicalCluster]*webhookserver.AdmissionWebhookServer{}
for _, cluster := range []logicalcluster.LogicalCluster{sourceWorkspace, targetWorkspace} {
testWebhooks[cluster] = &webhookserver.AdmissionWebhookServer{
Response: v1.AdmissionResponse{
Allowed: true,
},
},
}

_, err = kcpClients.Cluster(targetWorkspace).ApisV1alpha1().APIBindings().Create(ctx, apiBinding, metav1.CreateOptions{})
require.NoError(t, err)

scheme := runtime.NewScheme()
err = admissionregistrationv1.AddToScheme(scheme)
require.NoError(t, err, "failed to add admission registration v1 scheme")
err = v1.AddToScheme(scheme)
require.NoError(t, err, "failed to add admission v1 scheme")
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err, "failed to add cowboy v1alpha1 to scheme")
cowbyClients, err := client.NewClusterForConfig(cfg)
require.NoError(t, err, "failed to add cowboy v1alpha1 to scheme")
codecs := serializer.NewCodecFactory(scheme)
deserializer := codecs.UniversalDeserializer()

//Create Test Server and Create Validating Webhook for Cowboys in source cluster.
testWebhook := webhookserver.WebhookServer{
Response: v1.AdmissionResponse{
Allowed: true,
},
ObjectGVK: schema.GroupVersionKind{
Group: "wildwest.dev",
Version: "v1alpha1",
Kind: "Cowboy",
},
T: t,
Lock: sync.Mutex{},
Deserializer: deserializer,
}

port, err := framework.GetFreePort(t)
require.NoError(t, err, "failed to get free port for test webhook")
testWebhook.StartServer(ctx, server, port)

t.Logf("Installing webhook into the source workspace")
sideEffect := admissionregistrationv1.SideEffectClassNone
webhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{Name: "test-webhook"},
Webhooks: []admissionregistrationv1.ValidatingWebhook{{
Name: "test-webhook.cowboy.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: testWebhook.GetURL(),
CABundle: cfg.CAData,
ObjectGVK: schema.GroupVersionKind{
Group: "wildwest.dev",
Version: "v1alpha1",
Kind: "Cowboy",
},
Rules: []admissionregistrationv1.RuleWithOperations{{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"wildwest.dev"},
APIVersions: []string{"v1alpha1"},
Resources: []string{"cowboys"},
Deserializer: deserializer,
}
port, err := framework.GetFreePort(t)
require.NoError(t, err, "failed to get free port for test webhook")
dirPath := filepath.Dir(server.KubeconfigPath())
testWebhooks[cluster].StartTLS(t, filepath.Join(dirPath, "apiserver.crt"), filepath.Join(dirPath, "apiserver.key"), port)

sideEffect := admissionregistrationv1.SideEffectClassNone
url := testWebhooks[cluster].GetURL()
webhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{Name: "test-webhook"},
Webhooks: []admissionregistrationv1.ValidatingWebhook{{
Name: "test-webhook.cowboy.io",
ClientConfig: admissionregistrationv1.WebhookClientConfig{
URL: &url,
CABundle: cfg.CAData,
},
Rules: []admissionregistrationv1.RuleWithOperations{{
Operations: []admissionregistrationv1.OperationType{
admissionregistrationv1.Create,
},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{"wildwest.dev"},
APIVersions: []string{"v1alpha1"},
Resources: []string{"cowboys"},
},
}},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1"},
}},
SideEffects: &sideEffect,
AdmissionReviewVersions: []string{"v1"},
}},
}
_, err = kubeClusterClient.Cluster(cluster).AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, webhook, metav1.CreateOptions{})
require.NoError(t, err, "failed to add validating webhook configurations")
}
_, err = kubeClusterClient.Cluster(targetWorkspace).AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, webhook, metav1.CreateOptions{})
require.NoError(t, err, "failed to add validating webhook configurations")

cowboy := v1alpha1.Cowboy{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -452,7 +320,9 @@ func TestAPIBindingValidatingWebhookNotCalledWhenBound(t *testing.T) {
if err != nil && !errors.IsAlreadyExists(err) {
return false
}
return testWebhook.Calls == 0

return testWebhooks[sourceWorkspace].Calls() >= 1
}, wait.ForeverTestTimeout, 100*time.Millisecond)

t.Logf("Check that the in-workspace webhook was NOT called")
require.Zero(t, testWebhooks[targetWorkspace].Calls(), "in-workspace webhook should not have been called")
}
Loading

0 comments on commit 096d495

Please sign in to comment.