package controller import ( "fmt" "testing" kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" api "github.com/openshift/origin/pkg/api/latest" deployapi "github.com/openshift/origin/pkg/deploy/api" deploytest "github.com/openshift/origin/pkg/deploy/api/test" deployutil "github.com/openshift/origin/pkg/deploy/util" ) func TestHandleNewDeploymentCreatePodOk(t *testing.T) { var ( updatedDeployment *kapi.ReplicationController createdPod *kapi.Pod expectedContainer = okContainer() ) controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return updatedDeployment, nil }, }, PodClient: &DeploymentControllerPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { createdPod = pod return pod, nil }, }, ContainerCreator: &DeploymentContainerCreatorImpl{ CreateContainerFunc: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { return expectedContainer }, }, } // Verify new -> pending config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew) err := controller.HandleDeployment(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected an updated deployment") } if e, a := string(deployapi.DeploymentStatusPending), updatedDeployment.Annotations[deployapi.DeploymentStatusAnnotation]; e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } if createdPod == nil { t.Fatalf("expected a pod to be created") } if _, hasPodAnnotation := updatedDeployment.Annotations[deployapi.DeploymentPodAnnotation]; !hasPodAnnotation { t.Fatalf("missing deployment pod annotation") } if e, a := createdPod.Name, updatedDeployment.Annotations[deployapi.DeploymentPodAnnotation]; e != a { t.Fatalf("expected deployment pod annotation %s, got %s", e, a) } if _, hasDeploymentAnnotation := createdPod.Annotations[deployapi.DeploymentAnnotation]; !hasDeploymentAnnotation { t.Fatalf("missing deployment annotation") } if e, a := updatedDeployment.Name, createdPod.Annotations[deployapi.DeploymentAnnotation]; e != a { t.Fatalf("expected pod deployment annotation %s, got %s", e, a) } actualContainer := createdPod.Spec.Containers[0] if e, a := expectedContainer.Image, actualContainer.Image; e != a { t.Fatalf("expected container image %s, got %s", expectedContainer.Image, actualContainer.Image) } if e, a := expectedContainer.Command[0], actualContainer.Command[0]; e != a { t.Fatalf("expected container command %s, got %s", expectedContainer.Command[0], actualContainer.Command[0]) } if e, a := expectedContainer.Env[0].Name, actualContainer.Env[0].Name; e != a { t.Fatalf("expected container env name %s, got %s", expectedContainer.Env[0].Name, actualContainer.Env[0].Name) } if e, a := expectedContainer.Env[0].Value, actualContainer.Env[0].Value; e != a { t.Fatalf("expected container env value %s, got %s", expectedContainer.Env[0].Value, actualContainer.Env[0].Value) } } func TestHandleNewDeploymentCreatePodFail(t *testing.T) { var updatedDeployment *kapi.ReplicationController controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namspace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return updatedDeployment, nil }, }, PodClient: &DeploymentControllerPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { return nil, fmt.Errorf("Failed to create pod %s", pod.Name) }, }, ContainerCreator: &DeploymentContainerCreatorImpl{ CreateContainerFunc: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { return okContainer() }, }, } config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew) err := controller.HandleDeployment(deployment) if err == nil { t.Fatalf("expected an error") } } func TestHandleNewDeploymentCreatePodAlreadyExists(t *testing.T) { controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, nil }, }, PodClient: &DeploymentControllerPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { return nil, kerrors.NewAlreadyExists("Pod", pod.Name) }, }, ContainerCreator: &DeploymentContainerCreatorImpl{ CreateContainerFunc: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { return okContainer() }, }, } // Verify no-op config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusPending) err := controller.HandleDeployment(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestHandleDeploymentNoops(t *testing.T) { controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, nil }, }, PodClient: &DeploymentControllerPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { t.Fatalf("unexpected call to create pod") return nil, nil }, }, ContainerCreator: &DeploymentContainerCreatorImpl{ CreateContainerFunc: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to create container") return nil }, }, } // Verify no-op config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) noopStatus := []deployapi.DeploymentStatus{ deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning, deployapi.DeploymentStatusFailed, } for _, status := range noopStatus { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(status) err := controller.HandleDeployment(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } } } func TestHandleDeploymentPodCleanupOk(t *testing.T) { podName := "pod" deletedPodName := "" deletedPodNamespace := "" controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, nil }, }, PodClient: &DeploymentControllerPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { t.Fatalf("unexpected call to create pod") return nil, nil }, DeletePodFunc: func(namespace, name string) error { deletedPodNamespace = namespace deletedPodName = name return nil }, }, ContainerCreator: &DeploymentContainerCreatorImpl{ CreateContainerFunc: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to create container") return nil }, }, } // Verify successful cleanup config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusComplete) deployment.Annotations[deployapi.DeploymentPodAnnotation] = podName err := controller.HandleDeployment(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := deployment.Namespace, deletedPodNamespace; e != a { t.Fatalf("expected deleted pod namespace %s, got %s", e, a) } if e, a := podName, deletedPodName; e != a { t.Fatalf("expected deleted pod name %s, got %s", e, a) } } func TestHandleDeploymentPodCleanupNoop(t *testing.T) { controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, nil }, }, PodClient: &DeploymentControllerPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { t.Fatalf("unexpected call to create pod") return nil, nil }, DeletePodFunc: func(namespace, name string) error { return kerrors.NewNotFound("Pod", name) }, }, ContainerCreator: &DeploymentContainerCreatorImpl{ CreateContainerFunc: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to create container") return nil }, }, } // Verify no-op config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusComplete) deployment.Annotations[deployapi.DeploymentPodAnnotation] = "pod" err := controller.HandleDeployment(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } } func TestHandleDeploymentPodCleanupFailure(t *testing.T) { controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, nil }, }, PodClient: &DeploymentControllerPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { t.Fatalf("unexpected call to create pod") return nil, nil }, DeletePodFunc: func(namespace, name string) error { return kerrors.NewInternalError(fmt.Errorf("test error")) }, }, ContainerCreator: &DeploymentContainerCreatorImpl{ CreateContainerFunc: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { t.Fatalf("unexpected call to create container") return nil }, }, } // Verify error config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusComplete) deployment.Annotations[deployapi.DeploymentPodAnnotation] = "pod" err := controller.HandleDeployment(deployment) if err == nil { t.Fatalf("expected an error") } } func TestHandleUncorrelatedPod(t *testing.T) { controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, nil }, }, } // Verify no-op pod := runningPod() pod.Annotations = make(map[string]string) err := controller.HandlePod(pod) if err != nil { t.Fatalf("unexpected err: %v", err) } } func TestHandleOrphanedPod(t *testing.T) { controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("Unexpected deployment update") return nil, nil }, GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { return nil, kerrors.NewNotFound("ReplicationController", name) }, }, } err := controller.HandlePod(runningPod()) if err == nil { t.Fatalf("expected an error") } } func TestHandlePodRunning(t *testing.T) { var updatedDeployment *kapi.ReplicationController controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusPending) return deployment, nil }, UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return deployment, nil }, }, } err := controller.HandlePod(runningPod()) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusRunning, statusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } } func TestHandlePodTerminatedOk(t *testing.T) { var updatedDeployment *kapi.ReplicationController controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusRunning) return deployment, nil }, UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return deployment, nil }, }, } err := controller.HandlePod(succeededPod()) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusComplete, statusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } } func TestHandlePodTerminatedNotOk(t *testing.T) { var updatedDeployment *kapi.ReplicationController controller := &DeploymentController{ Codec: api.Codec, DeploymentClient: &DeploymentControllerDeploymentClientImpl{ GetDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusRunning) return deployment, nil }, UpdateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return deployment, nil }, }, } err := controller.HandlePod(failedPod()) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusFailed, statusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } } func okContainer() *kapi.Container { return &kapi.Container{ Image: "test/image", Command: []string{"command"}, Env: []kapi.EnvVar{ { Name: "env1", Value: "val1", }, }, } } func okPod() *kapi.Pod { return &kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ Name: "deploy-deploy1", Annotations: map[string]string{ deployapi.DeploymentAnnotation: "1234", }, }, Status: kapi.PodStatus{ Info: kapi.PodInfo{ "container1": kapi.ContainerStatus{}, }, }, } } func succeededPod() *kapi.Pod { p := okPod() p.Status.Phase = kapi.PodSucceeded return p } func failedPod() *kapi.Pod { p := okPod() p.Status.Phase = kapi.PodFailed p.Status.Info["container1"] = kapi.ContainerStatus{ State: kapi.ContainerState{ Termination: &kapi.ContainerStateTerminated{ ExitCode: 1, }, }, } return p } func runningPod() *kapi.Pod { p := okPod() p.Status.Phase = kapi.PodRunning return p }