package integration import ( "fmt" "io" "io/ioutil" "reflect" "testing" "k8s.io/kubernetes/pkg/admission" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/runtime" kyaml "k8s.io/kubernetes/pkg/util/yaml" buildapi "github.com/openshift/origin/pkg/build/api" "github.com/openshift/origin/pkg/client" configapi "github.com/openshift/origin/pkg/cmd/server/api" configapilatest "github.com/openshift/origin/pkg/cmd/server/api/latest" configapiv1 "github.com/openshift/origin/pkg/cmd/server/api/v1" testutil "github.com/openshift/origin/test/util" testserver "github.com/openshift/origin/test/util/server" ) type TestPluginConfig struct { unversioned.TypeMeta `json:",inline"` Data string `json:"data"` } func (obj *TestPluginConfig) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } func setupAdmissionTest(t *testing.T, setupConfig func(*configapi.MasterConfig)) (*kclientset.Clientset, *client.Client) { testutil.RequireEtcd(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } setupConfig(masterConfig) kubeConfigFile, err := testserver.StartConfiguredMasterAPI(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } kubeClient, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } openshiftClient, err := testutil.GetClusterAdminClient(kubeConfigFile) if err != nil { t.Fatalf("error getting openshift client: %v", err) } return kubeClient, openshiftClient } // testAdmissionPlugin sets a label with its name on the object getting admitted // on create type testAdmissionPlugin struct { name string labelValue string } func (p *testAdmissionPlugin) Admit(a admission.Attributes) (err error) { obj := a.GetObject() accessor, err := meta.Accessor(obj) if err != nil { return err } labels := accessor.GetLabels() if labels == nil { labels = map[string]string{} } if len(p.labelValue) > 0 { labels[p.name] = p.labelValue } else { labels[p.name] = "default" } accessor.SetLabels(labels) return nil } func (a *testAdmissionPlugin) Handles(operation admission.Operation) bool { return operation == admission.Create } func registerAdmissionPlugins(t *testing.T, names ...string) { for _, name := range names { pluginName := name admission.RegisterPlugin(pluginName, func(client kclientset.Interface, config io.Reader) (admission.Interface, error) { plugin := &testAdmissionPlugin{ name: pluginName, } if config != nil && !reflect.ValueOf(config).IsNil() { configData, err := ioutil.ReadAll(config) if err != nil { return nil, err } configData, err = kyaml.ToJSON(configData) if err != nil { return nil, err } configObj := &TestPluginConfig{} err = runtime.DecodeInto(kapi.Codecs.UniversalDecoder(), configData, configObj) if err != nil { return nil, err } plugin.labelValue = configObj.Data } return plugin, nil }) } } func admissionTestPod() *kapi.Pod { pod := &kapi.Pod{} pod.Name = "test-pod" container := kapi.Container{} container.Name = "foo" container.Image = "openshift/hello-openshift" pod.Spec.Containers = []kapi.Container{container} return pod } func admissionTestBuild() *buildapi.Build { build := &buildapi.Build{ObjectMeta: kapi.ObjectMeta{ Labels: map[string]string{ buildapi.BuildConfigLabel: "mock-build-config", buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel), }, }} build.Name = "test-build" build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "http://build.uri/build"} build.Spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{} build.Spec.Output.To = &kapi.ObjectReference{ Kind: "DockerImage", Name: "namespace/image", } return build } func checkAdmissionObjectLabelsIncludesExcludes(labels map[string]string, includes, excludes []string) error { for _, expected := range includes { if _, exists := labels[expected]; !exists { return fmt.Errorf("labels %v does not include expected label: %s", labels, expected) } } for _, notExpected := range excludes { if _, exists := labels[notExpected]; exists { return fmt.Errorf("labels %v includes unexpected label: %s", labels, notExpected) } } return nil } func checkAdmissionObjectLabelValues(labels, expected map[string]string) error { for k, v := range expected { if labels[k] != v { return fmt.Errorf("unexpected label value in %v for %s. Expected: %s", labels, k, v) } } return nil } func registerAdmissionPluginTestConfigType() { configapi.Scheme.AddKnownTypes(configapi.SchemeGroupVersion, &TestPluginConfig{}) configapi.Scheme.AddKnownTypes(configapiv1.SchemeGroupVersion, &TestPluginConfig{}) } func setupAdmissionPluginTestConfig(t *testing.T, value string) string { configFile, err := ioutil.TempFile("", "admission-config") if err != nil { t.Fatalf("error creating temp file: %v", err) } configFile.Close() configObj := &TestPluginConfig{ Data: value, } configContent, err := configapilatest.WriteYAML(configObj) if err != nil { t.Fatalf("error writing config: %v", err) } ioutil.WriteFile(configFile.Name(), configContent, 0644) return configFile.Name() } func TestKubernetesAdmissionPluginOrderOverride(t *testing.T) { defer testutil.DumpEtcdOnFailure(t) registerAdmissionPlugins(t, "plugin1", "plugin2", "plugin3") kubeClient, _ := setupAdmissionTest(t, func(config *configapi.MasterConfig) { config.KubernetesMasterConfig.AdmissionConfig.PluginOrderOverride = []string{"plugin1", "plugin2"} }) createdPod, err := kubeClient.Core().Pods(kapi.NamespaceDefault).Create(admissionTestPod()) if err != nil { t.Fatalf("Unexpected error creating pod: %v", err) } if err = checkAdmissionObjectLabelsIncludesExcludes(createdPod.Labels, []string{"plugin1", "plugin2"}, []string{"plugin3"}); err != nil { t.Errorf("Error: %v", err) } } func TestKubernetesAdmissionPluginConfigFile(t *testing.T) { defer testutil.DumpEtcdOnFailure(t) registerAdmissionPluginTestConfigType() configFile := setupAdmissionPluginTestConfig(t, "plugin1configvalue") registerAdmissionPlugins(t, "plugin1", "plugin2") kubeClient, _ := setupAdmissionTest(t, func(config *configapi.MasterConfig) { config.KubernetesMasterConfig.AdmissionConfig.PluginOrderOverride = []string{"plugin1", "plugin2"} config.KubernetesMasterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "plugin1": { Location: configFile, }, } }) createdPod, err := kubeClient.Core().Pods(kapi.NamespaceDefault).Create(admissionTestPod()) if err = checkAdmissionObjectLabelValues(createdPod.Labels, map[string]string{"plugin1": "plugin1configvalue", "plugin2": "default"}); err != nil { t.Errorf("Error: %v", err) } } func TestKubernetesAdmissionPluginEmbeddedConfig(t *testing.T) { defer testutil.DumpEtcdOnFailure(t) registerAdmissionPluginTestConfigType() registerAdmissionPlugins(t, "plugin1", "plugin2") kubeClient, _ := setupAdmissionTest(t, func(config *configapi.MasterConfig) { config.KubernetesMasterConfig.AdmissionConfig.PluginOrderOverride = []string{"plugin1", "plugin2"} config.KubernetesMasterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "plugin1": { Configuration: &TestPluginConfig{ Data: "embeddedvalue1", }, }, } }) createdPod, err := kubeClient.Core().Pods(kapi.NamespaceDefault).Create(admissionTestPod()) if err = checkAdmissionObjectLabelValues(createdPod.Labels, map[string]string{"plugin1": "embeddedvalue1", "plugin2": "default"}); err != nil { t.Errorf("Error: %v", err) } } func TestOpenshiftAdmissionPluginOrderOverride(t *testing.T) { defer testutil.DumpEtcdOnFailure(t) registerAdmissionPlugins(t, "plugin1", "plugin2", "plugin3") _, openshiftClient := setupAdmissionTest(t, func(config *configapi.MasterConfig) { config.AdmissionConfig.PluginOrderOverride = []string{"plugin1", "plugin2"} }) createdBuild, err := openshiftClient.Builds(kapi.NamespaceDefault).Create(admissionTestBuild()) if err != nil { t.Errorf("Unexpected error creating build: %v", err) } if err = checkAdmissionObjectLabelsIncludesExcludes(createdBuild.Labels, []string{"plugin1", "plugin2"}, []string{"plugin3"}); err != nil { t.Errorf("Error: %v", err) } } func TestOpenshiftAdmissionPluginConfigFile(t *testing.T) { defer testutil.DumpEtcdOnFailure(t) registerAdmissionPluginTestConfigType() configFile := setupAdmissionPluginTestConfig(t, "plugin2configvalue") registerAdmissionPlugins(t, "plugin1", "plugin2") _, openshiftClient := setupAdmissionTest(t, func(config *configapi.MasterConfig) { config.AdmissionConfig.PluginOrderOverride = []string{"plugin1", "plugin2"} config.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "plugin2": { Location: configFile, }, } }) createdBuild, err := openshiftClient.Builds(kapi.NamespaceDefault).Create(admissionTestBuild()) if err = checkAdmissionObjectLabelValues(createdBuild.Labels, map[string]string{"plugin1": "default", "plugin2": "plugin2configvalue"}); err != nil { t.Errorf("Error: %v", err) } } func TestOpenshiftAdmissionPluginEmbeddedConfig(t *testing.T) { defer testutil.DumpEtcdOnFailure(t) registerAdmissionPluginTestConfigType() registerAdmissionPlugins(t, "plugin1", "plugin2") _, openshiftClient := setupAdmissionTest(t, func(config *configapi.MasterConfig) { config.AdmissionConfig.PluginOrderOverride = []string{"plugin1", "plugin2"} config.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "plugin2": { Configuration: &TestPluginConfig{ Data: "embeddedvalue2", }, }, } }) createdBuild, err := openshiftClient.Builds(kapi.NamespaceDefault).Create(admissionTestBuild()) if err = checkAdmissionObjectLabelValues(createdBuild.Labels, map[string]string{"plugin1": "default", "plugin2": "embeddedvalue2"}); err != nil { t.Errorf("Error: %v", err) } } func TestAlwaysPullImagesOn(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) masterConfig, err := testserver.DefaultMasterOptions() if err != nil { t.Fatalf("error creating config: %v", err) } masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig = map[string]configapi.AdmissionPluginConfig{ "AlwaysPullImages": { Configuration: &configapi.DefaultAdmissionConfig{}, }, } kubeConfigFile, err := testserver.StartConfiguredMaster(masterConfig) if err != nil { t.Fatalf("error starting server: %v", err) } kubeClientset, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } ns := &kapi.Namespace{} ns.Name = testutil.Namespace() _, err = kubeClientset.Core().Namespaces().Create(ns) if err != nil { t.Fatalf("error creating namespace: %v", err) } if err := testserver.WaitForPodCreationServiceAccounts(kubeClientset, testutil.Namespace()); err != nil { t.Fatalf("error getting client config: %v", err) } testPod := &kapi.Pod{} testPod.GenerateName = "test" testPod.Spec.Containers = []kapi.Container{ { Name: "container", Image: "openshift/origin-pod:notlatest", ImagePullPolicy: kapi.PullNever, }, } actualPod, err := kubeClientset.Core().Pods(testutil.Namespace()).Create(testPod) if err != nil { t.Fatalf("unexpected error: %v", err) } if actualPod.Spec.Containers[0].ImagePullPolicy != kapi.PullAlways { t.Errorf("expected %v, got %v", kapi.PullAlways, actualPod.Spec.Containers[0].ImagePullPolicy) } } func TestAlwaysPullImagesOff(t *testing.T) { testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, kubeConfigFile, err := testserver.StartTestMaster() if err != nil { t.Fatalf("error starting server: %v", err) } kubeClientset, err := testutil.GetClusterAdminKubeClient(kubeConfigFile) if err != nil { t.Fatalf("error getting client: %v", err) } ns := &kapi.Namespace{} ns.Name = testutil.Namespace() _, err = kubeClientset.Core().Namespaces().Create(ns) if err != nil { t.Fatalf("error creating namespace: %v", err) } if err := testserver.WaitForPodCreationServiceAccounts(kubeClientset, testutil.Namespace()); err != nil { t.Fatalf("error getting client config: %v", err) } testPod := &kapi.Pod{} testPod.GenerateName = "test" testPod.Spec.Containers = []kapi.Container{ { Name: "container", Image: "openshift/origin-pod:notlatest", ImagePullPolicy: kapi.PullNever, }, } actualPod, err := kubeClientset.Core().Pods(testutil.Namespace()).Create(testPod) if err != nil { t.Fatalf("unexpected error: %v", err) } if actualPod.Spec.Containers[0].ImagePullPolicy != kapi.PullNever { t.Errorf("expected %v, got %v", kapi.PullNever, actualPod.Spec.Containers[0].ImagePullPolicy) } }