vendor update for CSI 0.3.0

This commit is contained in:
gman
2018-07-18 16:47:22 +02:00
parent 6f484f92fc
commit 8ea659f0d5
6810 changed files with 438061 additions and 193861 deletions

View File

@ -11,6 +11,7 @@ go_library(
"aggregator.go",
"certs.go",
"chunking.go",
"crd_watch.go",
"custom_resource_definition.go",
"etcd_failure.go",
"framework.go",
@ -19,12 +20,12 @@ go_library(
"initializers.go",
"namespace.go",
"table_conversion.go",
"watch.go",
"webhook.go",
],
importpath = "k8s.io/kubernetes/test/e2e/apimachinery",
deps = [
"//pkg/api/v1/pod:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/util/version:go_default_library",
@ -38,6 +39,7 @@ go_library(
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
"//vendor/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/authorization/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1:go_default_library",
"//vendor/k8s.io/api/batch/v1beta1:go_default_library",
@ -47,7 +49,9 @@ go_library(
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/test/integration/testserver:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1:go_default_library",
@ -55,6 +59,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",

View File

@ -24,8 +24,8 @@ import (
"strings"
"time"
apps "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -87,7 +87,7 @@ func cleanTest(client clientset.Interface, aggrclient *aggregatorclient.Clientse
// delete the APIService first to avoid causing discovery errors
_ = aggrclient.ApiregistrationV1beta1().APIServices().Delete("v1alpha1.wardle.k8s.io", nil)
_ = client.ExtensionsV1beta1().Deployments(namespace).Delete("sample-apiserver", nil)
_ = client.AppsV1().Deployments(namespace).Delete("sample-apiserver", nil)
_ = client.CoreV1().Secrets(namespace).Delete("sample-apiserver-secret", nil)
_ = client.CoreV1().Services(namespace).Delete("sample-api", nil)
_ = client.CoreV1().ServiceAccounts(namespace).Delete("sample-apiserver", nil)
@ -133,7 +133,7 @@ func TestSampleAPIServer(f *framework.Framework, image string) {
// kubectl create -f deploy.yaml
deploymentName := "sample-apiserver-deployment"
etcdImage := "quay.io/coreos/etcd:v3.2.14"
etcdImage := "quay.io/coreos/etcd:v3.2.18"
podLabels := map[string]string{"app": "sample-apiserver", "apiserver": "true"}
replicas := int32(1)
zero := int64(0)
@ -171,14 +171,18 @@ func TestSampleAPIServer(f *framework.Framework, image string) {
Image: etcdImage,
},
}
d := &extensions.Deployment{
d := &apps.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: deploymentName,
Name: deploymentName,
Labels: podLabels,
},
Spec: extensions.DeploymentSpec{
Spec: apps.DeploymentSpec{
Replicas: &replicas,
Strategy: extensions.DeploymentStrategy{
Type: extensions.RollingUpdateDeploymentStrategyType,
Selector: &metav1.LabelSelector{
MatchLabels: podLabels,
},
Strategy: apps.DeploymentStrategy{
Type: apps.RollingUpdateDeploymentStrategyType,
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
@ -192,7 +196,7 @@ func TestSampleAPIServer(f *framework.Framework, image string) {
},
},
}
deployment, err := client.ExtensionsV1beta1().Deployments(namespace).Create(d)
deployment, err := client.AppsV1().Deployments(namespace).Create(d)
framework.ExpectNoError(err, "creating deployment %s in namespace %s", deploymentName, namespace)
err = framework.WaitForDeploymentRevisionAndImage(client, namespace, deploymentName, "1", image)
framework.ExpectNoError(err, "waiting for the deployment of image %s in %s in %s to complete", image, deploymentName, namespace)
@ -315,7 +319,16 @@ func TestSampleAPIServer(f *framework.Framework, image string) {
})
framework.ExpectNoError(err, "creating apiservice %s with namespace %s", "v1alpha1.wardle.k8s.io", namespace)
err = wait.Poll(100*time.Millisecond, 30*time.Second, func() (bool, error) {
var (
currentAPIService *apiregistrationv1beta1.APIService
currentPods *v1.PodList
)
err = pollTimed(100*time.Millisecond, 60*time.Second, func() (bool, error) {
currentAPIService, _ = aggrclient.ApiregistrationV1beta1().APIServices().Get("v1alpha1.wardle.k8s.io", metav1.GetOptions{})
currentPods, _ = client.CoreV1().Pods(namespace).List(metav1.ListOptions{})
request := restClient.Get().AbsPath("/apis/wardle.k8s.io/v1alpha1/namespaces/default/flunders")
request.SetHeader("Accept", "application/json")
_, err := request.DoRaw()
@ -333,7 +346,23 @@ func TestSampleAPIServer(f *framework.Framework, image string) {
return false, err
}
return true, nil
})
}, "Waited %s for the sample-apiserver to be ready to handle requests.")
if err != nil {
currentAPIServiceJSON, _ := json.Marshal(currentAPIService)
framework.Logf("current APIService: %s", string(currentAPIServiceJSON))
currentPodsJSON, _ := json.Marshal(currentPods)
framework.Logf("current pods: %s", string(currentPodsJSON))
if currentPods != nil {
for _, pod := range currentPods.Items {
for _, container := range pod.Spec.Containers {
logs, err := framework.GetPodLogs(client, namespace, pod.Name, container.Name)
framework.Logf("logs of %s/%s (error: %v): %s", pod.Name, container.Name, err, logs)
}
}
}
}
framework.ExpectNoError(err, "gave up waiting for apiservice wardle to come up successfully")
flunderName := generateFlunderName("rest-flunder")
@ -382,19 +411,15 @@ func TestSampleAPIServer(f *framework.Framework, image string) {
flunderName = generateFlunderName("dynamic-flunder")
// Rerun the Create/List/Delete tests using the Dynamic client.
resources, err := client.Discovery().ServerPreferredNamespacedResources()
framework.ExpectNoError(err, "getting server preferred namespaces resources for dynamic client")
resources, discoveryErr := client.Discovery().ServerPreferredNamespacedResources()
groupVersionResources, err := discovery.GroupVersionResources(resources)
framework.ExpectNoError(err, "getting group version resources for dynamic client")
gvr := schema.GroupVersionResource{Group: "wardle.k8s.io", Version: "v1alpha1", Resource: "flunders"}
_, ok := groupVersionResources[gvr]
if !ok {
framework.Failf("could not find group version resource for dynamic client and wardle/flunders.")
framework.Failf("could not find group version resource for dynamic client and wardle/flunders (discovery error: %v, discovery results: %#v)", discoveryErr, groupVersionResources)
}
clientPool := f.ClientPool
dynamicClient, err := clientPool.ClientForGroupVersionResource(gvr)
framework.ExpectNoError(err, "getting group version resources for dynamic client")
apiResource := metav1.APIResource{Name: gvr.Resource, Namespaced: true}
dynamicClient := f.DynamicClient.Resource(gvr).Namespace(namespace)
// kubectl create -f flunders-1.yaml
// Request Body: {"apiVersion":"wardle.k8s.io/v1alpha1","kind":"Flunder","metadata":{"labels":{"sample-label":"true"},"name":"test-flunder","namespace":"default"}}
@ -411,27 +436,23 @@ func TestSampleAPIServer(f *framework.Framework, image string) {
unstruct := &unstructuredv1.Unstructured{}
err = unstruct.UnmarshalJSON(jsonFlunder)
framework.ExpectNoError(err, "unmarshalling test-flunder as unstructured for create using dynamic client")
unstruct, err = dynamicClient.Resource(&apiResource, namespace).Create(unstruct)
unstruct, err = dynamicClient.Create(unstruct)
framework.ExpectNoError(err, "listing flunders using dynamic client")
// kubectl get flunders
obj, err := dynamicClient.Resource(&apiResource, namespace).List(metav1.ListOptions{})
unstructuredList, err := dynamicClient.List(metav1.ListOptions{})
framework.ExpectNoError(err, "listing flunders using dynamic client")
unstructuredList, ok := obj.(*unstructuredv1.UnstructuredList)
validateErrorWithDebugInfo(f, err, pods, "casting flunders list(%T) as unstructuredList using dynamic client", obj)
if len(unstructuredList.Items) != 1 {
framework.Failf("failed to get back the correct flunders list %v from the dynamic client", unstructuredList)
}
// kubectl delete flunder test-flunder
err = dynamicClient.Resource(&apiResource, namespace).Delete(flunderName, &metav1.DeleteOptions{})
err = dynamicClient.Delete(flunderName, &metav1.DeleteOptions{})
validateErrorWithDebugInfo(f, err, pods, "deleting flunders(%v) using dynamic client", unstructuredList.Items)
// kubectl get flunders
obj, err = dynamicClient.Resource(&apiResource, namespace).List(metav1.ListOptions{})
unstructuredList, err = dynamicClient.List(metav1.ListOptions{})
framework.ExpectNoError(err, "listing flunders using dynamic client")
unstructuredList, ok = obj.(*unstructuredv1.UnstructuredList)
validateErrorWithDebugInfo(f, err, pods, "casting flunders list(%T) as unstructuredList using dynamic client", obj)
if len(unstructuredList.Items) != 0 {
framework.Failf("failed to get back the correct deleted flunders list %v from the dynamic client", unstructuredList)
}
@ -439,6 +460,17 @@ func TestSampleAPIServer(f *framework.Framework, image string) {
cleanTest(client, aggrclient, namespace)
}
// pollTimed will call Poll but time how long Poll actually took.
// It will then framework.logf the msg with the duration of the Poll.
// It is assumed that msg will contain one %s for the elapsed time.
func pollTimed(interval, timeout time.Duration, condition wait.ConditionFunc, msg string) error {
defer func(start time.Time, msg string) {
elapsed := time.Since(start)
framework.Logf(msg, elapsed)
}(time.Now(), msg)
return wait.Poll(interval, timeout, condition)
}
func validateErrorWithDebugInfo(f *framework.Framework, err error, pods *v1.PodList, msg string, fields ...interface{}) {
if err != nil {
namespace := f.Namespace.Name

View File

@ -0,0 +1,166 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apimachinery
import (
"fmt"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apiextensions-apiserver/test/integration/testserver"
"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/watch"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/test/e2e/framework"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = SIGDescribe("CustomResourceDefinition Watch", func() {
f := framework.NewDefaultFramework("crd-watch")
Context("CustomResourceDefinition Watch", func() {
/*
Testname: crd-watch
Description: Create a Custom Resource Definition and make sure
watches observe events on create/delete.
*/
It("watch on custom resource definition objects", func() {
framework.SkipUnlessServerVersionGTE(crdVersion, f.ClientSet.Discovery())
const (
watchCRNameA = "name1"
watchCRNameB = "name2"
)
config, err := framework.LoadConfig()
if err != nil {
framework.Failf("failed to load config: %v", err)
}
apiExtensionClient, err := clientset.NewForConfig(config)
if err != nil {
framework.Failf("failed to initialize apiExtensionClient: %v", err)
}
noxuDefinition := testserver.NewNoxuCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped)
noxuDefinition, err = testserver.CreateNewCustomResourceDefinition(noxuDefinition, apiExtensionClient, f.DynamicClient)
if err != nil {
framework.Failf("failed to create CustomResourceDefinition: %v", err)
}
defer func() {
err = testserver.DeleteCustomResourceDefinition(noxuDefinition, apiExtensionClient)
if err != nil {
framework.Failf("failed to delete CustomResourceDefinition: %v", err)
}
}()
ns := ""
noxuResourceClient := newNamespacedCustomResourceClient(ns, f.DynamicClient, noxuDefinition)
watchA, err := watchCRWithName(noxuResourceClient, watchCRNameA)
Expect(err).NotTo(HaveOccurred())
watchB, err := watchCRWithName(noxuResourceClient, watchCRNameB)
Expect(err).NotTo(HaveOccurred())
testCrA := testserver.NewNoxuInstance(ns, watchCRNameA)
testCrB := testserver.NewNoxuInstance(ns, watchCRNameB)
By("Creating first CR ")
testCrA, err = instantiateCustomResource(testCrA, noxuResourceClient, noxuDefinition)
Expect(err).NotTo(HaveOccurred())
expectEvent(watchA, watch.Added, testCrA)
expectNoEvent(watchB, watch.Added, testCrA)
By("Creating second CR")
testCrB, err = instantiateCustomResource(testCrB, noxuResourceClient, noxuDefinition)
Expect(err).NotTo(HaveOccurred())
expectEvent(watchB, watch.Added, testCrB)
expectNoEvent(watchA, watch.Added, testCrB)
By("Deleting first CR")
err = deleteCustomResource(noxuResourceClient, watchCRNameA)
Expect(err).NotTo(HaveOccurred())
expectEvent(watchA, watch.Deleted, nil)
expectNoEvent(watchB, watch.Deleted, nil)
By("Deleting second CR")
err = deleteCustomResource(noxuResourceClient, watchCRNameB)
Expect(err).NotTo(HaveOccurred())
expectEvent(watchB, watch.Deleted, nil)
expectNoEvent(watchA, watch.Deleted, nil)
})
})
})
func watchCRWithName(crdResourceClient dynamic.ResourceInterface, name string) (watch.Interface, error) {
return crdResourceClient.Watch(
metav1.ListOptions{
FieldSelector: "metadata.name=" + name,
TimeoutSeconds: int64ptr(600),
},
)
}
func instantiateCustomResource(instanceToCreate *unstructured.Unstructured, client dynamic.ResourceInterface, definition *apiextensionsv1beta1.CustomResourceDefinition) (*unstructured.Unstructured, error) {
createdInstance, err := client.Create(instanceToCreate)
if err != nil {
return nil, err
}
createdObjectMeta, err := meta.Accessor(createdInstance)
if err != nil {
return nil, err
}
// it should have a UUID
if len(createdObjectMeta.GetUID()) == 0 {
return nil, fmt.Errorf("missing uuid: %#v", createdInstance)
}
createdTypeMeta, err := meta.TypeAccessor(createdInstance)
if err != nil {
return nil, err
}
if e, a := definition.Spec.Group+"/"+definition.Spec.Version, createdTypeMeta.GetAPIVersion(); e != a {
return nil, fmt.Errorf("expected %v, got %v", e, a)
}
if e, a := definition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a {
return nil, fmt.Errorf("expected %v, got %v", e, a)
}
return createdInstance, nil
}
func deleteCustomResource(client dynamic.ResourceInterface, name string) error {
return client.Delete(name, &metav1.DeleteOptions{})
}
func newNamespacedCustomResourceClient(ns string, client dynamic.Interface, crd *apiextensionsv1beta1.CustomResourceDefinition) dynamic.ResourceInterface {
gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Version, Resource: crd.Spec.Names.Plural}
if crd.Spec.Scope != apiextensionsv1beta1.ClusterScoped {
return client.Resource(gvr).Namespace(ns)
} else {
return client.Resource(gvr)
}
}

View File

@ -55,7 +55,7 @@ var _ = SIGDescribe("CustomResourceDefinition resources", func() {
randomDefinition := testserver.NewRandomNameCustomResourceDefinition(v1beta1.ClusterScoped)
//create CRD and waits for the resource to be recognized and available.
_, err = testserver.CreateNewCustomResourceDefinition(randomDefinition, apiExtensionClient, f.ClientPool)
randomDefinition, err = testserver.CreateNewCustomResourceDefinition(randomDefinition, apiExtensionClient, f.DynamicClient)
if err != nil {
framework.Failf("failed to create CustomResourceDefinition: %v", err)
}

View File

@ -26,6 +26,7 @@ import (
"k8s.io/kubernetes/test/e2e/apps"
"k8s.io/kubernetes/test/e2e/framework"
testutils "k8s.io/kubernetes/test/utils"
imageutils "k8s.io/kubernetes/test/utils/image"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -47,7 +48,7 @@ var _ = SIGDescribe("Etcd failure [Disruptive]", func() {
Client: f.ClientSet,
Name: "baz",
Namespace: f.Namespace.Name,
Image: framework.GetPauseImageName(f.ClientSet),
Image: imageutils.GetPauseImageName(),
Replicas: 1,
})).NotTo(HaveOccurred())
})

View File

@ -18,6 +18,7 @@ package apimachinery
import (
"fmt"
"sync/atomic"
"time"
batchv1 "k8s.io/api/batch/v1"
@ -32,10 +33,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/storage/names"
clientset "k8s.io/client-go/kubernetes"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/test/e2e/framework"
"k8s.io/kubernetes/test/e2e/framework/metrics"
@ -88,8 +89,8 @@ func getOrphanOptions() *metav1.DeleteOptions {
}
var (
zero = int64(0)
zero = int64(0)
lablecount = int64(0)
CronJobGroupVersionResource = schema.GroupVersionResource{Group: batchv1beta1.GroupName, Version: "v1beta1", Resource: "cronjobs"}
)
@ -175,7 +176,7 @@ func verifyRemainingDeploymentsReplicaSetsPods(
}
if len(deployments.Items) != deploymentNum {
ret = false
By(fmt.Sprintf("expected %d Deploymentss, got %d Deployments", deploymentNum, len(deployments.Items)))
By(fmt.Sprintf("expected %d Deployments, got %d Deployments", deploymentNum, len(deployments.Items)))
}
pods, err := clientSet.CoreV1().Pods(f.Namespace.Name).List(metav1.ListOptions{})
if err != nil {
@ -323,6 +324,14 @@ func newCronJob(name, schedule string) *batchv1beta1.CronJob {
}
}
// getUniqLabel returns a UniqLabel based on labeLkey and labelvalue.
func getUniqLabel(labelkey, labelvalue string) map[string]string {
count := atomic.AddInt64(&lablecount, 1)
uniqlabelkey := fmt.Sprintf("%s-%05d", labelkey, count)
uniqlabelvalue := fmt.Sprintf("%s-%05d", labelvalue, count)
return map[string]string{uniqlabelkey: uniqlabelvalue}
}
var _ = SIGDescribe("Garbage collector", func() {
f := framework.NewDefaultFramework("gc")
@ -337,8 +346,7 @@ var _ = SIGDescribe("Garbage collector", func() {
rcClient := clientSet.CoreV1().ReplicationControllers(f.Namespace.Name)
podClient := clientSet.CoreV1().Pods(f.Namespace.Name)
rcName := "simpletest.rc"
// TODO: find better way to keep this label unique in the test
uniqLabels := map[string]string{"gctest": "delete_pods"}
uniqLabels := getUniqLabel("gctest", "delete_pods")
rc := newOwnerRC(f, rcName, 2, uniqLabels)
By("create the rc")
rc, err := rcClient.Create(rc)
@ -396,8 +404,7 @@ var _ = SIGDescribe("Garbage collector", func() {
rcClient := clientSet.CoreV1().ReplicationControllers(f.Namespace.Name)
podClient := clientSet.CoreV1().Pods(f.Namespace.Name)
rcName := "simpletest.rc"
// TODO: find better way to keep this label unique in the test
uniqLabels := map[string]string{"gctest": "orphan_pods"}
uniqLabels := getUniqLabel("gctest", "orphan_pods")
rc := newOwnerRC(f, rcName, estimateMaximumPods(clientSet, 10, 100), uniqLabels)
By("create the rc")
rc, err := rcClient.Create(rc)
@ -445,17 +452,13 @@ var _ = SIGDescribe("Garbage collector", func() {
framework.Failf("%v", err)
}
By("wait for 30 seconds to see if the garbage collector mistakenly deletes the pods")
if err := wait.Poll(5*time.Second, 30*time.Second, func() (bool, error) {
pods, err := podClient.List(metav1.ListOptions{})
if err != nil {
return false, fmt.Errorf("Failed to list pods: %v", err)
}
if e, a := int(*(rc.Spec.Replicas)), len(pods.Items); e != a {
return false, fmt.Errorf("expect %d pods, got %d pods", e, a)
}
return false, nil
}); err != nil && err != wait.ErrWaitTimeout {
framework.Failf("%v", err)
time.Sleep(30 * time.Second)
pods, err := podClient.List(metav1.ListOptions{})
if err != nil {
framework.Failf("Failed to list pods: %v", err)
}
if e, a := int(*(rc.Spec.Replicas)), len(pods.Items); e != a {
framework.Failf("expect %d pods, got %d pods", e, a)
}
gatherMetrics(f)
})
@ -465,8 +468,7 @@ var _ = SIGDescribe("Garbage collector", func() {
rcClient := clientSet.CoreV1().ReplicationControllers(f.Namespace.Name)
podClient := clientSet.CoreV1().Pods(f.Namespace.Name)
rcName := "simpletest.rc"
// TODO: find better way to keep this label unique in the test
uniqLabels := map[string]string{"gctest": "orphan_pods_nil_option"}
uniqLabels := getUniqLabel("gctest", "orphan_pods_nil_option")
rc := newOwnerRC(f, rcName, 2, uniqLabels)
By("create the rc")
rc, err := rcClient.Create(rc)
@ -494,17 +496,13 @@ var _ = SIGDescribe("Garbage collector", func() {
framework.Failf("failed to delete the rc: %v", err)
}
By("wait for 30 seconds to see if the garbage collector mistakenly deletes the pods")
if err := wait.Poll(5*time.Second, 30*time.Second, func() (bool, error) {
pods, err := podClient.List(metav1.ListOptions{})
if err != nil {
return false, fmt.Errorf("Failed to list pods: %v", err)
}
if e, a := int(*(rc.Spec.Replicas)), len(pods.Items); e != a {
return false, fmt.Errorf("expect %d pods, got %d pods", e, a)
}
return false, nil
}); err != nil && err != wait.ErrWaitTimeout {
framework.Failf("%v", err)
time.Sleep(30 * time.Second)
pods, err := podClient.List(metav1.ListOptions{})
if err != nil {
framework.Failf("Failed to list pods: %v", err)
}
if e, a := int(*(rc.Spec.Replicas)), len(pods.Items); e != a {
framework.Failf("expect %d pods, got %d pods", e, a)
}
gatherMetrics(f)
})
@ -520,8 +518,7 @@ var _ = SIGDescribe("Garbage collector", func() {
deployClient := clientSet.ExtensionsV1beta1().Deployments(f.Namespace.Name)
rsClient := clientSet.ExtensionsV1beta1().ReplicaSets(f.Namespace.Name)
deploymentName := "simpletest.deployment"
// TODO: find better way to keep this label unique in the test
uniqLabels := map[string]string{"gctest": "delete_rs"}
uniqLabels := getUniqLabel("gctest", "delete_rs")
deployment := newOwnerDeployment(f, deploymentName, uniqLabels)
By("create the deployment")
createdDeployment, err := deployClient.Create(deployment)
@ -552,14 +549,17 @@ var _ = SIGDescribe("Garbage collector", func() {
err = wait.PollImmediate(500*time.Millisecond, 1*time.Minute, func() (bool, error) {
return verifyRemainingDeploymentsReplicaSetsPods(f, clientSet, deployment, 0, 0, 0)
})
if err == wait.ErrWaitTimeout {
err = fmt.Errorf("Failed to wait for all rs to be garbage collected: %v", err)
if err != nil {
errList := make([]error, 0)
errList = append(errList, err)
remainingRSs, err := rsClient.List(metav1.ListOptions{})
if err != nil {
framework.Failf("failed to list RSs post mortem: %v", err)
errList = append(errList, fmt.Errorf("failed to list RSs post mortem: %v", err))
} else {
framework.Failf("remaining rs are: %#v", remainingRSs)
errList = append(errList, fmt.Errorf("remaining rs are: %#v", remainingRSs))
}
aggregatedError := utilerrors.NewAggregate(errList)
framework.Failf("Failed to wait for all rs to be garbage collected: %v", aggregatedError)
}
@ -577,8 +577,7 @@ var _ = SIGDescribe("Garbage collector", func() {
deployClient := clientSet.ExtensionsV1beta1().Deployments(f.Namespace.Name)
rsClient := clientSet.ExtensionsV1beta1().ReplicaSets(f.Namespace.Name)
deploymentName := "simpletest.deployment"
// TODO: find better way to keep this label unique in the test
uniqLabels := map[string]string{"gctest": "orphan_rs"}
uniqLabels := getUniqLabel("gctest", "orphan_rs")
deployment := newOwnerDeployment(f, deploymentName, uniqLabels)
By("create the deployment")
createdDeployment, err := deployClient.Create(deployment)
@ -605,24 +604,28 @@ var _ = SIGDescribe("Garbage collector", func() {
if err := deployClient.Delete(deployment.ObjectMeta.Name, deleteOptions); err != nil {
framework.Failf("failed to delete the deployment: %v", err)
}
By("wait for 2 Minute to see if the garbage collector mistakenly deletes the rs")
err = wait.PollImmediate(5*time.Second, 2*time.Minute, func() (bool, error) {
return verifyRemainingDeploymentsReplicaSetsPods(f, clientSet, deployment, 0, 1, 2)
})
By("wait for 30 seconds to see if the garbage collector mistakenly deletes the rs")
time.Sleep(30 * time.Second)
ok, err := verifyRemainingDeploymentsReplicaSetsPods(f, clientSet, deployment, 0, 1, 2)
if err != nil {
err = fmt.Errorf("Failed to wait to see if the garbage collecter mistakenly deletes the rs: %v", err)
framework.Failf("Unexpected error while verifying remaining deployments, rs, and pods: %v", err)
}
if !ok {
errList := make([]error, 0)
remainingRSs, err := rsClient.List(metav1.ListOptions{})
if err != nil {
framework.Failf("failed to list RSs post mortem: %v", err)
errList = append(errList, fmt.Errorf("failed to list RSs post mortem: %v", err))
} else {
framework.Failf("remaining rs post mortem: %#v", remainingRSs)
errList = append(errList, fmt.Errorf("remaining rs post mortem: %#v", remainingRSs))
}
remainingDSs, err := deployClient.List(metav1.ListOptions{})
if err != nil {
framework.Failf("failed to list Deployments post mortem: %v", err)
errList = append(errList, fmt.Errorf("failed to list Deployments post mortem: %v", err))
} else {
framework.Failf("remaining deployment's post mortem: %#v", remainingDSs)
errList = append(errList, fmt.Errorf("remaining deployment's post mortem: %#v", remainingDSs))
}
aggregatedError := utilerrors.NewAggregate(errList)
framework.Failf("Failed to verify remaining deployments, rs, and pods: %v", aggregatedError)
}
rs, err := clientSet.ExtensionsV1beta1().ReplicaSets(f.Namespace.Name).List(metav1.ListOptions{})
if err != nil {
@ -647,8 +650,7 @@ var _ = SIGDescribe("Garbage collector", func() {
rcClient := clientSet.CoreV1().ReplicationControllers(f.Namespace.Name)
podClient := clientSet.CoreV1().Pods(f.Namespace.Name)
rcName := "simpletest.rc"
// TODO: find better way to keep this label unique in the test
uniqLabels := map[string]string{"gctest": "delete_pods_foreground"}
uniqLabels := getUniqLabel("gctest", "delete_pods_foreground")
rc := newOwnerRC(f, rcName, estimateMaximumPods(clientSet, 10, 100), uniqLabels)
By("create the rc")
rc, err := rcClient.Create(rc)
@ -738,18 +740,16 @@ var _ = SIGDescribe("Garbage collector", func() {
rc1Name := "simpletest-rc-to-be-deleted"
replicas := int32(estimateMaximumPods(clientSet, 10, 100))
halfReplicas := int(replicas / 2)
// TODO: find better way to keep this label unique in the test
uniqLabels := map[string]string{"gctest": "valid_and_pending_owners"}
rc1 := newOwnerRC(f, rc1Name, replicas, uniqLabels)
uniqLabels_deleted := getUniqLabel("gctest_d", "valid_and_pending_owners_d")
rc1 := newOwnerRC(f, rc1Name, replicas, uniqLabels_deleted)
By("create the rc1")
rc1, err := rcClient.Create(rc1)
if err != nil {
framework.Failf("Failed to create replication controller: %v", err)
}
rc2Name := "simpletest-rc-to-stay"
// TODO: find better way to keep this label unique in the test
uniqLabels = map[string]string{"another.key": "another.value"}
rc2 := newOwnerRC(f, rc2Name, 0, uniqLabels)
uniqLabels_stay := getUniqLabel("gctest_s", "valid_and_pending_owners_s")
rc2 := newOwnerRC(f, rc2Name, 0, uniqLabels_stay)
By("create the rc2")
rc2, err = rcClient.Create(rc2)
if err != nil {
@ -917,16 +917,15 @@ var _ = SIGDescribe("Garbage collector", func() {
framework.Failf("failed to delete CustomResourceDefinition: %v", err)
}
}()
client, err := apiextensionstestserver.CreateNewCustomResourceDefinition(definition, apiExtensionClient, f.ClientPool)
definition, err = apiextensionstestserver.CreateNewCustomResourceDefinition(definition, apiExtensionClient, f.DynamicClient)
if err != nil {
framework.Failf("failed to create CustomResourceDefinition: %v", err)
}
// Get a client for the custom resource.
resourceClient := client.Resource(&metav1.APIResource{
Name: definition.Spec.Names.Plural,
Namespaced: false,
}, api.NamespaceNone)
gvr := schema.GroupVersionResource{Group: definition.Spec.Group, Version: definition.Spec.Version, Resource: definition.Spec.Names.Plural}
resourceClient := f.DynamicClient.Resource(gvr)
apiVersion := definition.Spec.Group + "/" + definition.Spec.Version
// Create a custom owner resource.
@ -999,8 +998,111 @@ var _ = SIGDescribe("Garbage collector", func() {
}
})
It("should support orphan deletion of custom resources", func() {
config, err := framework.LoadConfig()
if err != nil {
framework.Failf("failed to load config: %v", err)
}
apiExtensionClient, err := apiextensionsclientset.NewForConfig(config)
if err != nil {
framework.Failf("failed to initialize apiExtensionClient: %v", err)
}
// Create a random custom resource definition and ensure it's available for
// use.
definition := apiextensionstestserver.NewRandomNameCustomResourceDefinition(apiextensionsv1beta1.ClusterScoped)
defer func() {
err = apiextensionstestserver.DeleteCustomResourceDefinition(definition, apiExtensionClient)
if err != nil && !errors.IsNotFound(err) {
framework.Failf("failed to delete CustomResourceDefinition: %v", err)
}
}()
definition, err = apiextensionstestserver.CreateNewCustomResourceDefinition(definition, apiExtensionClient, f.DynamicClient)
if err != nil {
framework.Failf("failed to create CustomResourceDefinition: %v", err)
}
// Get a client for the custom resource.
gvr := schema.GroupVersionResource{Group: definition.Spec.Group, Version: definition.Spec.Version, Resource: definition.Spec.Names.Plural}
resourceClient := f.DynamicClient.Resource(gvr)
apiVersion := definition.Spec.Group + "/" + definition.Spec.Version
// Create a custom owner resource.
ownerName := names.SimpleNameGenerator.GenerateName("owner")
owner := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": definition.Spec.Names.Kind,
"metadata": map[string]interface{}{
"name": ownerName,
},
},
}
persistedOwner, err := resourceClient.Create(owner)
if err != nil {
framework.Failf("failed to create owner resource %q: %v", ownerName, err)
}
framework.Logf("created owner resource %q", ownerName)
// Create a custom dependent resource.
dependentName := names.SimpleNameGenerator.GenerateName("dependent")
dependent := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": apiVersion,
"kind": definition.Spec.Names.Kind,
"metadata": map[string]interface{}{
"name": dependentName,
"ownerReferences": []map[string]string{
{
"uid": string(persistedOwner.GetUID()),
"apiVersion": apiVersion,
"kind": definition.Spec.Names.Kind,
"name": ownerName,
},
},
},
},
}
_, err = resourceClient.Create(dependent)
if err != nil {
framework.Failf("failed to create dependent resource %q: %v", dependentName, err)
}
framework.Logf("created dependent resource %q", dependentName)
// Delete the owner and orphan the dependent.
err = resourceClient.Delete(ownerName, getOrphanOptions())
if err != nil {
framework.Failf("failed to delete owner resource %q: %v", ownerName, err)
}
By("wait for the owner to be deleted")
if err := wait.Poll(5*time.Second, 120*time.Second, func() (bool, error) {
_, err = resourceClient.Get(ownerName, metav1.GetOptions{})
if err == nil {
return false, nil
}
if err != nil && !errors.IsNotFound(err) {
return false, fmt.Errorf("Failed to get owner: %v", err)
}
return true, nil
}); err != nil {
framework.Failf("timeout in waiting for the owner to be deleted: %v", err)
}
// Wait 30s and ensure the dependent is not deleted.
By("wait for 30 seconds to see if the garbage collector mistakenly deletes the dependent crd")
if err := wait.Poll(5*time.Second, 30*time.Second, func() (bool, error) {
_, err := resourceClient.Get(dependentName, metav1.GetOptions{})
return false, err
}); err != nil && err != wait.ErrWaitTimeout {
framework.Failf("failed to ensure the dependent is not deleted: %v", err)
}
})
It("should delete jobs and pods created by cronjob", func() {
framework.SkipIfMissingResource(f.ClientPool, CronJobGroupVersionResource, f.Namespace.Name)
framework.SkipIfMissingResource(f.DynamicClient, CronJobGroupVersionResource, f.Namespace.Name)
By("Create the cronjob")
cronJob := newCronJob("simple", "*/1 * * * ?")

View File

@ -264,7 +264,7 @@ var _ = SIGDescribe("Generated clientset", func() {
f := framework.NewDefaultFramework("clientset")
BeforeEach(func() {
framework.SkipIfMissingResource(f.ClientPool, CronJobGroupVersionResource, f.Namespace.Name)
framework.SkipIfMissingResource(f.DynamicClient, CronJobGroupVersionResource, f.Namespace.Name)
})
It("should create v1beta1 cronJobs, delete cronJobs, watch cronJobs", func() {

View File

@ -29,6 +29,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/test/e2e/framework"
imageutils "k8s.io/kubernetes/test/utils/image"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -114,7 +115,7 @@ func ensurePodsAreRemovedWhenNamespaceIsDeleted(f *framework.Framework) {
Containers: []v1.Container{
{
Name: "nginx",
Image: framework.GetPauseImageName(f.ClientSet),
Image: imageutils.GetPauseImageName(),
},
},
},
@ -135,7 +136,7 @@ func ensurePodsAreRemovedWhenNamespaceIsDeleted(f *framework.Framework) {
Containers: []v1.Container{
{
Name: "nginx",
Image: framework.GetPauseImageName(f.ClientSet),
Image: imageutils.GetPauseImageName(),
},
},
},

View File

@ -32,13 +32,20 @@ import (
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/printers"
utilversion "k8s.io/kubernetes/pkg/util/version"
"k8s.io/kubernetes/test/e2e/framework"
imageutils "k8s.io/kubernetes/test/utils/image"
)
var serverPrintVersion = utilversion.MustParseSemantic("v1.10.0")
var _ = SIGDescribe("Servers with support for Table transformation", func() {
f := framework.NewDefaultFramework("tables")
BeforeEach(func() {
framework.SkipUnlessServerVersionGTE(serverPrintVersion, f.ClientSet.Discovery())
})
It("should return pod details", func() {
ns := f.Namespace.Name
c := f.ClientSet

383
vendor/k8s.io/kubernetes/test/e2e/apimachinery/watch.go generated vendored Normal file
View File

@ -0,0 +1,383 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apimachinery
import (
"time"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/test/e2e/framework"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const (
watchConfigMapLabelKey = "watch-this-configmap"
multipleWatchersLabelValueA = "multiple-watchers-A"
multipleWatchersLabelValueB = "multiple-watchers-B"
fromResourceVersionLabelValue = "from-resource-version"
watchRestartedLabelValue = "watch-closed-and-restarted"
toBeChangedLabelValue = "label-changed-and-restored"
)
var _ = SIGDescribe("Watchers", func() {
f := framework.NewDefaultFramework("watch")
/*
Testname: watch-configmaps-with-multiple-watchers
Description: Ensure that multiple watchers are able to receive all add,
update, and delete notifications on configmaps that match a label selector and do
not receive notifications for configmaps which do not match that label selector.
*/
framework.ConformanceIt("should observe add, update, and delete watch notifications on configmaps", func() {
c := f.ClientSet
ns := f.Namespace.Name
By("creating a watch on configmaps with label A")
watchA, err := watchConfigMaps(f, "", multipleWatchersLabelValueA)
Expect(err).NotTo(HaveOccurred())
By("creating a watch on configmaps with label B")
watchB, err := watchConfigMaps(f, "", multipleWatchersLabelValueB)
Expect(err).NotTo(HaveOccurred())
By("creating a watch on configmaps with label A or B")
watchAB, err := watchConfigMaps(f, "", multipleWatchersLabelValueA, multipleWatchersLabelValueB)
Expect(err).NotTo(HaveOccurred())
testConfigMapA := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "e2e-watch-test-configmap-a",
Labels: map[string]string{
watchConfigMapLabelKey: multipleWatchersLabelValueA,
},
},
}
testConfigMapB := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "e2e-watch-test-configmap-b",
Labels: map[string]string{
watchConfigMapLabelKey: multipleWatchersLabelValueB,
},
},
}
By("creating a configmap with label A and ensuring the correct watchers observe the notification")
testConfigMapA, err = c.CoreV1().ConfigMaps(ns).Create(testConfigMapA)
Expect(err).NotTo(HaveOccurred())
expectEvent(watchA, watch.Added, testConfigMapA)
expectEvent(watchAB, watch.Added, testConfigMapA)
expectNoEvent(watchB, watch.Added, testConfigMapA)
By("modifying configmap A and ensuring the correct watchers observe the notification")
testConfigMapA, err = updateConfigMap(c, ns, testConfigMapA.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "1")
})
Expect(err).NotTo(HaveOccurred())
expectEvent(watchA, watch.Modified, testConfigMapA)
expectEvent(watchAB, watch.Modified, testConfigMapA)
expectNoEvent(watchB, watch.Modified, testConfigMapA)
By("modifying configmap A again and ensuring the correct watchers observe the notification")
testConfigMapA, err = updateConfigMap(c, ns, testConfigMapA.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "2")
})
Expect(err).NotTo(HaveOccurred())
expectEvent(watchA, watch.Modified, testConfigMapA)
expectEvent(watchAB, watch.Modified, testConfigMapA)
expectNoEvent(watchB, watch.Modified, testConfigMapA)
By("deleting configmap A and ensuring the correct watchers observe the notification")
err = c.CoreV1().ConfigMaps(ns).Delete(testConfigMapA.GetName(), nil)
Expect(err).NotTo(HaveOccurred())
expectEvent(watchA, watch.Deleted, nil)
expectEvent(watchAB, watch.Deleted, nil)
expectNoEvent(watchB, watch.Deleted, nil)
By("creating a configmap with label B and ensuring the correct watchers observe the notification")
testConfigMapB, err = c.CoreV1().ConfigMaps(ns).Create(testConfigMapB)
Expect(err).NotTo(HaveOccurred())
expectEvent(watchB, watch.Added, testConfigMapB)
expectEvent(watchAB, watch.Added, testConfigMapB)
expectNoEvent(watchA, watch.Added, testConfigMapB)
By("deleting configmap B and ensuring the correct watchers observe the notification")
err = c.CoreV1().ConfigMaps(ns).Delete(testConfigMapB.GetName(), nil)
Expect(err).NotTo(HaveOccurred())
expectEvent(watchB, watch.Deleted, nil)
expectEvent(watchAB, watch.Deleted, nil)
expectNoEvent(watchA, watch.Deleted, nil)
})
/*
Testname: watch-configmaps-from-resource-version
Description: Ensure that a watch can be opened from a particular resource version
in the past and only notifications happening after that resource version are observed.
*/
framework.ConformanceIt("should be able to start watching from a specific resource version", func() {
c := f.ClientSet
ns := f.Namespace.Name
testConfigMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "e2e-watch-test-resource-version",
Labels: map[string]string{
watchConfigMapLabelKey: fromResourceVersionLabelValue,
},
},
}
By("creating a new configmap")
testConfigMap, err := c.CoreV1().ConfigMaps(ns).Create(testConfigMap)
Expect(err).NotTo(HaveOccurred())
By("modifying the configmap once")
testConfigMapFirstUpdate, err := updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "1")
})
Expect(err).NotTo(HaveOccurred())
By("modifying the configmap a second time")
testConfigMapSecondUpdate, err := updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "2")
})
Expect(err).NotTo(HaveOccurred())
By("deleting the configmap")
err = c.CoreV1().ConfigMaps(ns).Delete(testConfigMap.GetName(), nil)
Expect(err).NotTo(HaveOccurred())
By("creating a watch on configmaps from the resource version returned by the first update")
testWatch, err := watchConfigMaps(f, testConfigMapFirstUpdate.ObjectMeta.ResourceVersion, fromResourceVersionLabelValue)
Expect(err).NotTo(HaveOccurred())
By("Expecting to observe notifications for all changes to the configmap after the first update")
expectEvent(testWatch, watch.Modified, testConfigMapSecondUpdate)
expectEvent(testWatch, watch.Deleted, nil)
})
/*
Testname: watch-configmaps-closed-and-restarted
Description: Ensure that a watch can be reopened from the last resource version
observed by the previous watch, and it will continue delivering notifications from
that point in time.
*/
framework.ConformanceIt("should be able to restart watching from the last resource version observed by the previous watch", func() {
c := f.ClientSet
ns := f.Namespace.Name
testConfigMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "e2e-watch-test-watch-closed",
Labels: map[string]string{
watchConfigMapLabelKey: watchRestartedLabelValue,
},
},
}
By("creating a watch on configmaps")
testWatchBroken, err := watchConfigMaps(f, "", watchRestartedLabelValue)
Expect(err).NotTo(HaveOccurred())
By("creating a new configmap")
testConfigMap, err = c.CoreV1().ConfigMaps(ns).Create(testConfigMap)
Expect(err).NotTo(HaveOccurred())
By("modifying the configmap once")
_, err = updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "1")
})
Expect(err).NotTo(HaveOccurred())
By("closing the watch once it receives two notifications")
expectEvent(testWatchBroken, watch.Added, testConfigMap)
lastEvent, ok := waitForEvent(testWatchBroken, watch.Modified, nil, 1*time.Minute)
if !ok {
framework.Failf("Timed out waiting for second watch notification")
}
testWatchBroken.Stop()
By("modifying the configmap a second time, while the watch is closed")
testConfigMapSecondUpdate, err := updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "2")
})
Expect(err).NotTo(HaveOccurred())
By("creating a new watch on configmaps from the last resource version observed by the first watch")
lastEventConfigMap, ok := lastEvent.Object.(*v1.ConfigMap)
if !ok {
framework.Failf("Expected last notfication to refer to a configmap but got: %v", lastEvent)
}
testWatchRestarted, err := watchConfigMaps(f, lastEventConfigMap.ObjectMeta.ResourceVersion, watchRestartedLabelValue)
Expect(err).NotTo(HaveOccurred())
By("deleting the configmap")
err = c.CoreV1().ConfigMaps(ns).Delete(testConfigMap.GetName(), nil)
Expect(err).NotTo(HaveOccurred())
By("Expecting to observe notifications for all changes to the configmap since the first watch closed")
expectEvent(testWatchRestarted, watch.Modified, testConfigMapSecondUpdate)
expectEvent(testWatchRestarted, watch.Deleted, nil)
})
/*
Testname: watch-configmaps-label-changed
Description: Ensure that a watched object stops meeting the requirements of
a watch's selector, the watch will observe a delete, and will not observe
notifications for that object until it meets the selector's requirements again.
*/
framework.ConformanceIt("should observe an object deletion if it stops meeting the requirements of the selector", func() {
c := f.ClientSet
ns := f.Namespace.Name
testConfigMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "e2e-watch-test-label-changed",
Labels: map[string]string{
watchConfigMapLabelKey: toBeChangedLabelValue,
},
},
}
By("creating a watch on configmaps with a certain label")
testWatch, err := watchConfigMaps(f, "", toBeChangedLabelValue)
Expect(err).NotTo(HaveOccurred())
By("creating a new configmap")
testConfigMap, err = c.CoreV1().ConfigMaps(ns).Create(testConfigMap)
Expect(err).NotTo(HaveOccurred())
By("modifying the configmap once")
testConfigMapFirstUpdate, err := updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "1")
})
Expect(err).NotTo(HaveOccurred())
By("changing the label value of the configmap")
_, err = updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
cm.ObjectMeta.Labels[watchConfigMapLabelKey] = "wrong-value"
})
Expect(err).NotTo(HaveOccurred())
By("Expecting to observe a delete notification for the watched object")
expectEvent(testWatch, watch.Added, testConfigMap)
expectEvent(testWatch, watch.Modified, testConfigMapFirstUpdate)
expectEvent(testWatch, watch.Deleted, nil)
By("modifying the configmap a second time")
testConfigMapSecondUpdate, err := updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "2")
})
Expect(err).NotTo(HaveOccurred())
By("Expecting not to observe a notification because the object no longer meets the selector's requirements")
expectNoEvent(testWatch, watch.Modified, testConfigMapSecondUpdate)
By("changing the label value of the configmap back")
testConfigMapLabelRestored, err := updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
cm.ObjectMeta.Labels[watchConfigMapLabelKey] = toBeChangedLabelValue
})
Expect(err).NotTo(HaveOccurred())
By("modifying the configmap a third time")
testConfigMapThirdUpdate, err := updateConfigMap(c, ns, testConfigMap.GetName(), func(cm *v1.ConfigMap) {
setConfigMapData(cm, "mutation", "3")
})
Expect(err).NotTo(HaveOccurred())
By("deleting the configmap")
err = c.CoreV1().ConfigMaps(ns).Delete(testConfigMap.GetName(), nil)
Expect(err).NotTo(HaveOccurred())
By("Expecting to observe an add notification for the watched object when the label value was restored")
expectEvent(testWatch, watch.Added, testConfigMapLabelRestored)
expectEvent(testWatch, watch.Modified, testConfigMapThirdUpdate)
expectEvent(testWatch, watch.Deleted, nil)
})
})
func watchConfigMaps(f *framework.Framework, resourceVersion string, labels ...string) (watch.Interface, error) {
c := f.ClientSet
ns := f.Namespace.Name
opts := metav1.ListOptions{
ResourceVersion: resourceVersion,
LabelSelector: metav1.FormatLabelSelector(&metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: watchConfigMapLabelKey,
Operator: metav1.LabelSelectorOpIn,
Values: labels,
},
},
}),
}
return c.CoreV1().ConfigMaps(ns).Watch(opts)
}
func int64ptr(i int) *int64 {
i64 := int64(i)
return &i64
}
func setConfigMapData(cm *v1.ConfigMap, key, value string) {
if cm.Data == nil {
cm.Data = make(map[string]string)
}
cm.Data[key] = value
}
func expectEvent(w watch.Interface, eventType watch.EventType, object runtime.Object) {
if event, ok := waitForEvent(w, eventType, object, 1*time.Minute); !ok {
framework.Failf("Timed out waiting for expected watch notification: %v", event)
}
}
func expectNoEvent(w watch.Interface, eventType watch.EventType, object runtime.Object) {
if event, ok := waitForEvent(w, eventType, object, 10*time.Second); ok {
framework.Failf("Unexpected watch notification observed: %v", event)
}
}
func waitForEvent(w watch.Interface, expectType watch.EventType, expectObject runtime.Object, duration time.Duration) (watch.Event, bool) {
stopTimer := time.NewTimer(duration)
defer stopTimer.Stop()
for {
select {
case actual, ok := <-w.ResultChan():
if ok {
framework.Logf("Got : %v %v", actual.Type, actual.Object)
} else {
framework.Failf("Watch closed unexpectedly")
}
if expectType == actual.Type && (expectObject == nil || apiequality.Semantic.DeepEqual(expectObject, actual.Object)) {
return actual, true
}
case <-stopTimer.C:
expected := watch.Event{
Type: expectType,
Object: expectObject,
}
return expected, false
}
}
}

View File

@ -23,10 +23,11 @@ import (
"time"
"k8s.io/api/admissionregistration/v1beta1"
apps "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
extensions "k8s.io/api/extensions/v1beta1"
rbacv1beta1 "k8s.io/api/rbac/v1beta1"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -51,12 +52,16 @@ const (
roleBindingName = "webhook-auth-reader"
// The webhook configuration names should not be reused between test instances.
crdWebhookConfigName = "e2e-test-webhook-config-crd"
crWebhookConfigName = "e2e-test-webhook-config-cr"
webhookConfigName = "e2e-test-webhook-config"
mutatingWebhookConfigName = "e2e-test-mutating-webhook-config"
podMutatingWebhookConfigName = "e2e-test-mutating-webhook-pod"
crdMutatingWebhookConfigName = "e2e-test-mutating-webhook-config-crd"
crMutatingWebhookConfigName = "e2e-test-mutating-webhook-config-cr"
webhookFailClosedConfigName = "e2e-test-webhook-fail-closed"
webhookForWebhooksConfigName = "e2e-test-webhook-for-webhooks-config"
removableValidatingHookName = "e2e-test-should-be-removable-validating-webhook-config"
removableMutatingHookName = "e2e-test-should-be-removable-mutating-webhook-config"
crdWebhookConfigName = "e2e-test-webhook-config-crd"
skipNamespaceLabelKey = "skip-webhook-admission"
skipNamespaceLabelValue = "yes"
@ -118,9 +123,9 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
return
}
defer testcrd.CleanUp()
webhookCleanup := registerWebhookForCRD(f, context, testcrd)
webhookCleanup := registerWebhookForCustomResource(f, context, testcrd)
defer webhookCleanup()
testCRDWebhook(f, testcrd.Crd, testcrd.DynamicClient)
testCustomResourceWebhook(f, testcrd.Crd, testcrd.DynamicClient)
})
It("Should unconditionally reject operations on fail closed webhook", func() {
@ -141,15 +146,28 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
testMutatingPodWebhook(f)
})
It("Should mutate crd", func() {
It("Should not be able to prevent deleting validating-webhook-configurations or mutating-webhook-configurations", func() {
webhookCleanup := registerWebhookForWebhookConfigurations(f, context)
defer webhookCleanup()
testWebhookForWebhookConfigurations(f)
})
It("Should mutate custom resource", func() {
testcrd, err := framework.CreateTestCRD(f)
if err != nil {
return
}
defer testcrd.CleanUp()
webhookCleanup := registerMutatingWebhookForCRD(f, context, testcrd)
webhookCleanup := registerMutatingWebhookForCustomResource(f, context, testcrd)
defer webhookCleanup()
testMutatingCRDWebhook(f, testcrd.Crd, testcrd.DynamicClient)
testMutatingCustomResourceWebhook(f, testcrd.Crd, testcrd.DynamicClient)
})
It("Should deny crd creation", func() {
crdWebhookCleanup := registerValidatingWebhookForCRD(f, context)
defer crdWebhookCleanup()
testCRDDenyWebhook(f)
})
// TODO: add more e2e tests for mutating webhooks
@ -245,14 +263,18 @@ func deployWebhookAndService(f *framework.Framework, image string, context *cert
Image: image,
},
}
d := &extensions.Deployment{
d := &apps.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: deploymentName,
Name: deploymentName,
Labels: podLabels,
},
Spec: extensions.DeploymentSpec{
Spec: apps.DeploymentSpec{
Replicas: &replicas,
Strategy: extensions.DeploymentStrategy{
Type: extensions.RollingUpdateDeploymentStrategyType,
Selector: &metav1.LabelSelector{
MatchLabels: podLabels,
},
Strategy: apps.DeploymentStrategy{
Type: apps.RollingUpdateDeploymentStrategyType,
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
@ -266,7 +288,7 @@ func deployWebhookAndService(f *framework.Framework, image string, context *cert
},
},
}
deployment, err := client.ExtensionsV1beta1().Deployments(namespace).Create(d)
deployment, err := client.AppsV1().Deployments(namespace).Create(d)
framework.ExpectNoError(err, "creating deployment %s in namespace %s", deploymentName, namespace)
By("Wait for the deployment to be ready")
err = framework.WaitForDeploymentRevisionAndImage(client, namespace, deploymentName, "1", image)
@ -375,7 +397,7 @@ func registerWebhook(f *framework.Framework, context *certContext) func() {
})
framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
// The webhook configuration is honored in 1s.
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
return func() {
@ -437,7 +459,7 @@ func registerMutatingWebhookForConfigMap(f *framework.Framework, context *certCo
})
framework.ExpectNoError(err, "registering mutating webhook config %s with namespace %s", configName, namespace)
// The webhook configuration is honored in 1s.
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
return func() { client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Delete(configName, nil) }
}
@ -493,7 +515,7 @@ func registerMutatingWebhookForPod(f *framework.Framework, context *certContext)
})
framework.ExpectNoError(err, "registering mutating webhook config %s with namespace %s", configName, namespace)
// The webhook configuration is honored in 1s.
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
return func() { client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Delete(configName, nil) }
@ -525,7 +547,7 @@ func toBeMutatedPod(f *framework.Framework) *v1.Pod {
Containers: []v1.Container{
{
Name: "example",
Image: framework.GetPauseImageName(f.ClientSet),
Image: imageutils.GetPauseImageName(),
},
},
},
@ -709,6 +731,154 @@ func testFailClosedWebhook(f *framework.Framework) {
}
}
func registerWebhookForWebhookConfigurations(f *framework.Framework, context *certContext) func() {
var err error
client := f.ClientSet
By("Registering a webhook on ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects, via the AdmissionRegistration API")
namespace := f.Namespace.Name
configName := webhookForWebhooksConfigName
failurePolicy := v1beta1.Fail
// This webhook will deny all requests to Delete admissionregistration objects
_, err = client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&v1beta1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: configName,
},
Webhooks: []v1beta1.Webhook{
{
Name: "deny-webhook-configuration-deletions.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Delete},
Rule: v1beta1.Rule{
APIGroups: []string{"admissionregistration.k8s.io"},
APIVersions: []string{"*"},
Resources: []string{
"validatingwebhookconfigurations",
"mutatingwebhookconfigurations",
},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
Path: strPtr("/always-deny"),
},
CABundle: context.signingCert,
},
FailurePolicy: &failurePolicy,
},
},
})
framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
return func() {
err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Delete(configName, nil)
framework.ExpectNoError(err, "deleting webhook config %s with namespace %s", configName, namespace)
}
}
// This test assumes that the deletion-rejecting webhook defined in
// registerWebhookForWebhookConfigurations is in place.
func testWebhookForWebhookConfigurations(f *framework.Framework) {
var err error
client := f.ClientSet
By("Creating a validating-webhook-configuration object")
namespace := f.Namespace.Name
failurePolicy := v1beta1.Ignore
_, err = client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&v1beta1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: removableValidatingHookName,
},
Webhooks: []v1beta1.Webhook{
{
Name: "should-be-removable-validating-webhook.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
// This will not match any real resources so this webhook should never be called.
Rule: v1beta1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"invalid"},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
// This path not recognized by the webhook service,
// so the call to this webhook will always fail,
// but because the failure policy is ignore, it will
// have no effect on admission requests.
Path: strPtr(""),
},
CABundle: nil,
},
FailurePolicy: &failurePolicy,
},
},
})
framework.ExpectNoError(err, "registering webhook config %s with namespace %s", removableValidatingHookName, namespace)
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
By("Deleting the validating-webhook-configuration, which should be possible to remove")
err = client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Delete(removableValidatingHookName, nil)
framework.ExpectNoError(err, "deleting webhook config %s with namespace %s", removableValidatingHookName, namespace)
By("Creating a mutating-webhook-configuration object")
_, err = client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&v1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: removableMutatingHookName,
},
Webhooks: []v1beta1.Webhook{
{
Name: "should-be-removable-mutating-webhook.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
// This will not match any real resources so this webhook should never be called.
Rule: v1beta1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"invalid"},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
// This path not recognized by the webhook service,
// so the call to this webhook will always fail,
// but because the failure policy is ignore, it will
// have no effect on admission requests.
Path: strPtr(""),
},
CABundle: nil,
},
FailurePolicy: &failurePolicy,
},
},
})
framework.ExpectNoError(err, "registering webhook config %s with namespace %s", removableMutatingHookName, namespace)
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
By("Deleting the mutating-webhook-configuration, which should be possible to remove")
err = client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Delete(removableMutatingHookName, nil)
framework.ExpectNoError(err, "deleting webhook config %s with namespace %s", removableMutatingHookName, namespace)
}
func createNamespace(f *framework.Framework, ns *v1.Namespace) error {
return wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
_, err := f.ClientSet.CoreV1().Namespaces().Create(ns)
@ -734,7 +904,7 @@ func nonCompliantPod(f *framework.Framework) *v1.Pod {
Containers: []v1.Container{
{
Name: "webhook-disallow",
Image: framework.GetPauseImageName(f.ClientSet),
Image: imageutils.GetPauseImageName(),
},
},
},
@ -753,7 +923,7 @@ func hangingPod(f *framework.Framework) *v1.Pod {
Containers: []v1.Container{
{
Name: "wait-forever",
Image: framework.GetPauseImageName(f.ClientSet),
Image: imageutils.GetPauseImageName(),
},
},
},
@ -810,24 +980,24 @@ func updateConfigMap(c clientset.Interface, ns, name string, update updateConfig
func cleanWebhookTest(client clientset.Interface, namespaceName string) {
_ = client.CoreV1().Services(namespaceName).Delete(serviceName, nil)
_ = client.ExtensionsV1beta1().Deployments(namespaceName).Delete(deploymentName, nil)
_ = client.AppsV1().Deployments(namespaceName).Delete(deploymentName, nil)
_ = client.CoreV1().Secrets(namespaceName).Delete(secretName, nil)
_ = client.RbacV1beta1().RoleBindings("kube-system").Delete(roleBindingName, nil)
}
func registerWebhookForCRD(f *framework.Framework, context *certContext, testcrd *framework.TestCrd) func() {
func registerWebhookForCustomResource(f *framework.Framework, context *certContext, testcrd *framework.TestCrd) func() {
client := f.ClientSet
By("Registering the crd webhook via the AdmissionRegistration API")
By("Registering the custom resource webhook via the AdmissionRegistration API")
namespace := f.Namespace.Name
configName := crdWebhookConfigName
configName := crWebhookConfigName
_, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&v1beta1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: configName,
},
Webhooks: []v1beta1.Webhook{
{
Name: "deny-unwanted-crd-data.k8s.io",
Name: "deny-unwanted-custom-resource-data.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
Rule: v1beta1.Rule{
@ -836,6 +1006,161 @@ func registerWebhookForCRD(f *framework.Framework, context *certContext, testcrd
Resources: []string{testcrd.GetPluralName()},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
Path: strPtr("/custom-resource"),
},
CABundle: context.signingCert,
},
},
},
})
framework.ExpectNoError(err, "registering custom resource webhook config %s with namespace %s", configName, namespace)
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
return func() {
client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Delete(configName, nil)
}
}
func registerMutatingWebhookForCustomResource(f *framework.Framework, context *certContext, testcrd *framework.TestCrd) func() {
client := f.ClientSet
By("Registering the mutating webhook for a custom resource via the AdmissionRegistration API")
namespace := f.Namespace.Name
configName := crMutatingWebhookConfigName
_, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&v1beta1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: configName,
},
Webhooks: []v1beta1.Webhook{
{
Name: "mutate-custom-resource-data-stage-1.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
Rule: v1beta1.Rule{
APIGroups: []string{testcrd.ApiGroup},
APIVersions: []string{testcrd.ApiVersion},
Resources: []string{testcrd.GetPluralName()},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
Path: strPtr("/mutating-custom-resource"),
},
CABundle: context.signingCert,
},
},
{
Name: "mutate-custom-resource-data-stage-2.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
Rule: v1beta1.Rule{
APIGroups: []string{testcrd.ApiGroup},
APIVersions: []string{testcrd.ApiVersion},
Resources: []string{testcrd.GetPluralName()},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
Path: strPtr("/mutating-custom-resource"),
},
CABundle: context.signingCert,
},
},
},
})
framework.ExpectNoError(err, "registering custom resource webhook config %s with namespace %s", configName, namespace)
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
return func() { client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Delete(configName, nil) }
}
func testCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
By("Creating a custom resource that should be denied by the webhook")
crInstance := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": crd.Spec.Names.Kind,
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version,
"metadata": map[string]interface{}{
"name": "cr-instance-1",
"namespace": f.Namespace.Name,
},
"data": map[string]interface{}{
"webhook-e2e-test": "webhook-disallow",
},
},
}
_, err := customResourceClient.Create(crInstance)
Expect(err).NotTo(BeNil())
expectedErrMsg := "the custom resource contains unwanted data"
if !strings.Contains(err.Error(), expectedErrMsg) {
framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
}
}
func testMutatingCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
By("Creating a custom resource that should be mutated by the webhook")
cr := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": crd.Spec.Names.Kind,
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version,
"metadata": map[string]interface{}{
"name": "cr-instance-1",
"namespace": f.Namespace.Name,
},
"data": map[string]interface{}{
"mutation-start": "yes",
},
},
}
mutatedCR, err := customResourceClient.Create(cr)
Expect(err).To(BeNil())
expectedCRData := map[string]interface{}{
"mutation-start": "yes",
"mutation-stage-1": "yes",
"mutation-stage-2": "yes",
}
if !reflect.DeepEqual(expectedCRData, mutatedCR.Object["data"]) {
framework.Failf("\nexpected %#v\n, got %#v\n", expectedCRData, mutatedCR.Object["data"])
}
}
func registerValidatingWebhookForCRD(f *framework.Framework, context *certContext) func() {
client := f.ClientSet
By("Registering the crd webhook via the AdmissionRegistration API")
namespace := f.Namespace.Name
configName := crdWebhookConfigName
// This webhook will deny the creation of CustomResourceDefinitions which have the
// label "webhook-e2e-test":"webhook-disallow"
// NOTE: Because tests are run in parallel and in an unpredictable order, it is critical
// that no other test attempts to create CRD with that label.
_, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&v1beta1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: configName,
},
Webhooks: []v1beta1.Webhook{
{
Name: "deny-crd-with-unwanted-label.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
Rule: v1beta1.Rule{
APIGroups: []string{"apiextensions.k8s.io"},
APIVersions: []string{"*"},
Resources: []string{"customresourcedefinitions"},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
@ -849,118 +1174,62 @@ func registerWebhookForCRD(f *framework.Framework, context *certContext, testcrd
})
framework.ExpectNoError(err, "registering crd webhook config %s with namespace %s", configName, namespace)
// The webhook configuration is honored in 1s.
// The webhook configuration is honored in 10s.
time.Sleep(10 * time.Second)
return func() {
client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Delete(configName, nil)
}
}
func registerMutatingWebhookForCRD(f *framework.Framework, context *certContext, testcrd *framework.TestCrd) func() {
client := f.ClientSet
By("Registering the mutating webhook for crd via the AdmissionRegistration API")
func testCRDDenyWebhook(f *framework.Framework) {
By("Creating a custom resource definition that should be denied by the webhook")
name := fmt.Sprintf("e2e-test-%s-%s-crd", f.BaseName, "deny")
kind := fmt.Sprintf("E2e-test-%s-%s-crd", f.BaseName, "deny")
group := fmt.Sprintf("%s-crd-test.k8s.io", f.BaseName)
apiVersion := "v1"
testcrd := &framework.TestCrd{
Name: name,
Kind: kind,
ApiGroup: group,
ApiVersion: apiVersion,
}
namespace := f.Namespace.Name
configName := crdMutatingWebhookConfigName
_, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&v1beta1.MutatingWebhookConfiguration{
// Creating a custom resource definition for use by assorted tests.
config, err := framework.LoadConfig()
if err != nil {
framework.Failf("failed to load config: %v", err)
return
}
apiExtensionClient, err := crdclientset.NewForConfig(config)
if err != nil {
framework.Failf("failed to initialize apiExtensionClient: %v", err)
return
}
crd := &apiextensionsv1beta1.CustomResourceDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: configName,
},
Webhooks: []v1beta1.Webhook{
{
Name: "mutate-crd-data-stage-1.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
Rule: v1beta1.Rule{
APIGroups: []string{testcrd.ApiGroup},
APIVersions: []string{testcrd.ApiVersion},
Resources: []string{testcrd.GetPluralName()},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
Path: strPtr("/mutating-crd"),
},
CABundle: context.signingCert,
},
},
{
Name: "mutate-crd-data-stage-2.k8s.io",
Rules: []v1beta1.RuleWithOperations{{
Operations: []v1beta1.OperationType{v1beta1.Create},
Rule: v1beta1.Rule{
APIGroups: []string{testcrd.ApiGroup},
APIVersions: []string{testcrd.ApiVersion},
Resources: []string{testcrd.GetPluralName()},
},
}},
ClientConfig: v1beta1.WebhookClientConfig{
Service: &v1beta1.ServiceReference{
Namespace: namespace,
Name: serviceName,
Path: strPtr("/mutating-crd"),
},
CABundle: context.signingCert,
},
},
},
})
framework.ExpectNoError(err, "registering crd webhook config %s with namespace %s", configName, namespace)
// The webhook configuration is honored in 1s.
time.Sleep(10 * time.Second)
return func() { client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Delete(configName, nil) }
}
func testCRDWebhook(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, crdClient dynamic.ResourceInterface) {
By("Creating a custom resource that should be denied by the webhook")
crInstance := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": crd.Spec.Names.Kind,
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version,
"metadata": map[string]interface{}{
"name": "cr-instance-1",
"namespace": f.Namespace.Name,
},
"data": map[string]interface{}{
Name: testcrd.GetMetaName(),
Labels: map[string]string{
"webhook-e2e-test": "webhook-disallow",
},
},
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
Group: testcrd.ApiGroup,
Version: testcrd.ApiVersion,
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
Plural: testcrd.GetPluralName(),
Singular: testcrd.Name,
Kind: testcrd.Kind,
ListKind: testcrd.GetListName(),
},
Scope: apiextensionsv1beta1.NamespaceScoped,
},
}
_, err := crdClient.Create(crInstance)
// create CRD
_, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
Expect(err).NotTo(BeNil())
expectedErrMsg := "the custom resource contains unwanted data"
expectedErrMsg := "the crd contains unwanted label"
if !strings.Contains(err.Error(), expectedErrMsg) {
framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
}
}
func testMutatingCRDWebhook(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, crdClient dynamic.ResourceInterface) {
By("Creating a custom resource that should be mutated by the webhook")
cr := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": crd.Spec.Names.Kind,
"apiVersion": crd.Spec.Group + "/" + crd.Spec.Version,
"metadata": map[string]interface{}{
"name": "cr-instance-1",
"namespace": f.Namespace.Name,
},
"data": map[string]interface{}{
"mutation-start": "yes",
},
},
}
mutatedCR, err := crdClient.Create(cr)
Expect(err).To(BeNil())
expectedCRData := map[string]interface{}{
"mutation-start": "yes",
"mutation-stage-1": "yes",
"mutation-stage-2": "yes",
}
if !reflect.DeepEqual(expectedCRData, mutatedCR.Object["data"]) {
framework.Failf("\nexpected %#v\n, got %#v\n", expectedCRData, mutatedCR.Object["data"])
}
}