diff --git a/cmd/kube-apiserver/app/testing/BUILD b/cmd/kube-apiserver/app/testing/BUILD index d25a92a7d96..ce33e30777a 100644 --- a/cmd/kube-apiserver/app/testing/BUILD +++ b/cmd/kube-apiserver/app/testing/BUILD @@ -14,12 +14,15 @@ go_test( library = ":go_default_library", tags = ["automanaged"], deps = [ + "//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library", "//vendor/k8s.io/api/apps/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/networking/v1:go_default_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/apimachinery/pkg/api/errors: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/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", diff --git a/cmd/kube-apiserver/app/testing/server_test.go b/cmd/kube-apiserver/app/testing/server_test.go index 8dc30f90f7b..cc228e57c24 100644 --- a/cmd/kube-apiserver/app/testing/server_test.go +++ b/cmd/kube-apiserver/app/testing/server_test.go @@ -17,16 +17,20 @@ limitations under the License. package testing import ( + "encoding/json" "fmt" "testing" "time" + admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1" appsv1beta1 "k8s.io/api/apps/v1beta1" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsclientset "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" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/dynamic" @@ -149,6 +153,11 @@ func TestCRD(t *testing.T) { config, tearDown := StartTestServerOrDie(t) defer tearDown() + kubeclient, err := kubernetes.NewForConfig(config) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + apiextensionsclient, err := apiextensionsclientset.NewForConfig(config) if err != nil { t.Fatalf("Unexpected error: %v", err) @@ -193,6 +202,152 @@ func TestCRD(t *testing.T) { if err != nil { t.Errorf("Failed to list foos.cr.bar.com instances: %v", err) } + + t.Logf("Creating InitializerConfiguration") + _, err = kubeclient.AdmissionregistrationV1alpha1().InitializerConfigurations().Create(&admissionregistrationv1alpha1.InitializerConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foos.cr.bar.com", + }, + Initializers: []admissionregistrationv1alpha1.Initializer{ + { + Name: "cr.bar.com", + Rules: []admissionregistrationv1alpha1.Rule{ + { + APIGroups: []string{"cr.bar.com"}, + APIVersions: []string{"*"}, + Resources: []string{"*"}, + }, + }, + }, + }, + }) + if err != nil { + t.Fatalf("Failed to create InitializerConfiguration: %v", err) + } + + // TODO DO NOT MERGE THIS + time.Sleep(5 * time.Second) + + t.Logf("Creating Foo instance") + foo := &Foo{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "cr.bar.com/v1", + Kind: "Foo", + }, + ObjectMeta: metav1.ObjectMeta{Name: "foo"}, + } + unstructuredFoo, err := unstructuredFoo(foo) + if err != nil { + t.Fatalf("Unable to create Foo: %v", err) + } + createErr := make(chan error, 1) + go func() { + _, err = barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Create(unstructuredFoo) + t.Logf("Foo instance create returned: %v", err) + if err != nil { + createErr <- err + } + }() + + err = wait.PollImmediate(100*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) { + select { + case createErr := <-createErr: + return true, createErr + default: + } + + t.Logf("Checking that Foo instance is visible with IncludeUninitialized=true") + _, err := barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Get(foo.ObjectMeta.Name, metav1.GetOptions{ + IncludeUninitialized: true, + }) + switch { + case err == nil: + return true, nil + case errors.IsNotFound(err): + return false, nil + default: + return false, err + } + }) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + t.Logf("Removing initializer from Foo instance") + success := false + for i := 0; i < 10; i++ { + // would love to replace the following with a patch, but removing strings from the intitializer array + // is not what JSON (Merge) patch authors had in mind. + fooUnstructured, err := barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Get(foo.ObjectMeta.Name, metav1.GetOptions{ + IncludeUninitialized: true, + }) + if err != nil { + t.Fatalf("Error getting Foo instance: %v", err) + } + bs, _ := fooUnstructured.MarshalJSON() + t.Logf("Got Foo instance: %v", string(bs)) + foo := Foo{} + if err := json.Unmarshal(bs, &foo); err != nil { + t.Fatalf("Error parsing Foo instance: %v", err) + } + + // remove initialize + if foo.ObjectMeta.Initializers == nil { + t.Fatalf("Expected initializers to be set in Foo instance") + } + found := false + for i := range foo.ObjectMeta.Initializers.Pending { + if foo.ObjectMeta.Initializers.Pending[i].Name == "cr.bar.com" { + foo.ObjectMeta.Initializers.Pending = append(foo.ObjectMeta.Initializers.Pending[:i], foo.ObjectMeta.Initializers.Pending[i+1:]...) + found = true + break + } + } + if !found { + t.Fatalf("Expected cr.bar.com as initializer on Foo instance") + } + if len(foo.ObjectMeta.Initializers.Pending) == 0 && foo.ObjectMeta.Initializers.Result == nil { + foo.ObjectMeta.Initializers = nil + } + bs, err = json.Marshal(&foo) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + fooUnstructured.UnmarshalJSON(bs) + + _, err = barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Update(fooUnstructured) + if err != nil && !errors.IsConflict(err) { + t.Fatalf("Failed to update Foo instance: %v", err) + } else if err == nil { + success = true + break + } + } + if !success { + t.Fatalf("Failed to remove initializer from Foo object") + } + + t.Logf("Checking that Foo instance is visible after removing the initializer") + if _, err := barComClient.Resource(&metav1.APIResource{Name: "foos", Namespaced: true}, "default").Get(foo.ObjectMeta.Name, metav1.GetOptions{}); err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +type Foo struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` +} + +func unstructuredFoo(foo *Foo) (*unstructured.Unstructured, error) { + bs, err := json.Marshal(foo) + if err != nil { + return nil, err + } + ret := &unstructured.Unstructured{} + if err = ret.UnmarshalJSON(bs); err != nil { + return nil, err + } + return ret, nil } func waitForEstablishedCRD(client apiextensionsclientset.Interface, name string) error { diff --git a/cmd/kube-apiserver/app/testing/testserver.go b/cmd/kube-apiserver/app/testing/testserver.go index d7cd9787609..d1a26dc7592 100644 --- a/cmd/kube-apiserver/app/testing/testserver.go +++ b/cmd/kube-apiserver/app/testing/testserver.go @@ -79,6 +79,7 @@ func StartTestServer(t *testing.T) (result *restclient.Config, tearDownForCaller s.Etcd.StorageConfig = *storageConfig s.Etcd.DefaultStorageMediaType = "application/json" s.Admission.PluginNames = strings.Split("Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds", ",") + s.APIEnablement.RuntimeConfig.Set("api/all=true") t.Logf("Starting kube-apiserver...") runErrCh := make(chan error, 1)