package controller import ( "fmt" "github.com/golang/glog" kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" kerrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" deployapi "github.com/openshift/origin/pkg/deploy/api" deployutil "github.com/openshift/origin/pkg/deploy/util" ) // DeploymentController performs a deployment by creating a pod which is defined by a strategy. // The status of the resulting deployment will follow the status of the corresponding pod. // // Deployments are represented by a ReplicationController. type DeploymentController struct { // ContainerCreator makes the container for the deployment pod based on the strategy. ContainerCreator DeploymentContainerCreator // DeploymentClient provides access to deployments. DeploymentClient DeploymentControllerDeploymentClient // PodClient provides access to pods. PodClient DeploymentControllerPodClient // NextDeployment blocks until the next deployment is available. NextDeployment func() *kapi.ReplicationController // NextPod blocks until the next pod is available. NextPod func() *kapi.Pod // Environment is a set of environment which should be injected into all deployment pod // containers, in addition to whatever environment is specified by the ContainerCreator. Environment []kapi.EnvVar // Codec is used to decode DeploymentConfigs. Codec runtime.Codec // Stop is an optional channel that controls when the controller exits. Stop <-chan struct{} } // Run begins watching and synchronizing deployment states. func (dc *DeploymentController) Run() { go util.Until(func() { err := dc.HandleDeployment(dc.NextDeployment()) if err != nil { glog.Errorf("%v", err) } }, 0, dc.Stop) go util.Until(func() { err := dc.HandlePod(dc.NextPod()) if err != nil { glog.Errorf("%v", err) } }, 0, dc.Stop) } // HandleDeployment processes a new deployment and creates a new Pod which implements the specific // deployment behavior. The deployment and pod are correlated with annotations. If the pod was // successfully created, the deployment's status is transitioned to pending. func (dc *DeploymentController) HandleDeployment(deployment *kapi.ReplicationController) error { currentStatus := statusFor(deployment) nextStatus := currentStatus switch currentStatus { case deployapi.DeploymentStatusNew: podTemplate, err := dc.makeDeployerPod(deployment) if err != nil { return fmt.Errorf("couldn't make deployer pod for %s: %v", labelForDeployment(deployment), err) } deploymentPod, err := dc.PodClient.CreatePod(deployment.Namespace, podTemplate) if err != nil { // If the pod already exists, it's possible that a previous CreatePod succeeded but // the deployment state update failed and now we're re-entering. if !kerrors.IsAlreadyExists(err) { return fmt.Errorf("couldn't create deployer pod for %s: %v", labelForDeployment(deployment), err) } } else { glog.V(2).Infof("Created pod %s for deployment %s", deploymentPod.Name, labelForDeployment(deployment)) } deployment.Annotations[deployapi.DeploymentPodAnnotation] = deploymentPod.Name nextStatus = deployapi.DeploymentStatusPending case deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning, deployapi.DeploymentStatusFailed: glog.V(4).Infof("Ignoring deployment %s (status %s)", labelForDeployment(deployment), currentStatus) case deployapi.DeploymentStatusComplete: // Automatically clean up successful pods // TODO: Could probably do a lookup here to skip the delete call, but it's not worth adding // yet since (delete retries will only normally occur during full a re-sync). podName := deployment.Annotations[deployapi.DeploymentPodAnnotation] if err := dc.PodClient.DeletePod(deployment.Namespace, podName); err != nil { if !kerrors.IsNotFound(err) { return fmt.Errorf("couldn't delete completed deployer pod %s/%s for deployment %s: %v", deployment.Namespace, podName, labelForDeployment(deployment), err) } // Already deleted } else { glog.V(4).Infof("Deleted completed deployer pod %s/%s for deployment %s", deployment.Namespace, podName, labelForDeployment(deployment)) } } if currentStatus != nextStatus { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) if _, err := dc.DeploymentClient.UpdateDeployment(deployment.Namespace, deployment); err != nil { return fmt.Errorf("Couldn't update deployment %s to status %s: %v", labelForDeployment(deployment), nextStatus, err) } glog.V(2).Infof("Updated deployment %s status from %s to %s", labelForDeployment(deployment), currentStatus, nextStatus) } return nil } // HandlePod reconciles a pod's current state with its associated deployment and updates the // deployment appropriately. func (dc *DeploymentController) HandlePod(pod *kapi.Pod) error { // Verify the assumption that we'll be given only pods correlated to a deployment deploymentName, hasDeploymentName := pod.Annotations[deployapi.DeploymentAnnotation] if !hasDeploymentName { glog.V(2).Infof("Ignoring pod %s; no deployment annotation found", pod.Name) return nil } deployment, deploymentErr := dc.DeploymentClient.GetDeployment(pod.Namespace, deploymentName) if deploymentErr != nil { return fmt.Errorf("couldn't get deployment %s/%s associated with pod %s", pod.Namespace, deploymentName, pod.Name) } currentStatus := statusFor(deployment) nextStatus := currentStatus switch pod.Status.Phase { case kapi.PodRunning: nextStatus = deployapi.DeploymentStatusRunning case kapi.PodSucceeded, kapi.PodFailed: nextStatus = deployapi.DeploymentStatusComplete // Detect failure based on the container state for _, info := range pod.Status.Info { if info.State.Termination != nil && info.State.Termination.ExitCode != 0 { nextStatus = deployapi.DeploymentStatusFailed } } } if currentStatus != nextStatus { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) if _, err := dc.DeploymentClient.UpdateDeployment(deployment.Namespace, deployment); err != nil { return fmt.Errorf("couldn't update deployment %s to status %s: %v", labelForDeployment(deployment), nextStatus, err) } glog.V(2).Infof("Updated deployment %s status from %s to %s", labelForDeployment(deployment), currentStatus, nextStatus) } return nil } // makeDeployerPod creates a pod which implements deployment behavior. The pod is correlated to // the deployment with an annotation. func (dc *DeploymentController) makeDeployerPod(deployment *kapi.ReplicationController) (*kapi.Pod, error) { var deploymentConfig *deployapi.DeploymentConfig var decodeError error if deploymentConfig, decodeError = deployutil.DecodeDeploymentConfig(deployment, dc.Codec); decodeError != nil { return nil, decodeError } container := dc.ContainerCreator.CreateContainer(&deploymentConfig.Template.Strategy) // Combine the container environment, controller environment, and deployment values into // the pod's environment. envVars := container.Env envVars = append(envVars, kapi.EnvVar{Name: "OPENSHIFT_DEPLOYMENT_NAME", Value: deployment.Name}) envVars = append(envVars, kapi.EnvVar{Name: "OPENSHIFT_DEPLOYMENT_NAMESPACE", Value: deployment.Namespace}) for _, env := range dc.Environment { envVars = append(envVars, env) } pod := &kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ GenerateName: deployutil.DeployerPodNameForDeployment(deployment), Annotations: map[string]string{ deployapi.DeploymentAnnotation: deployment.Name, }, }, Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "deployment", Command: container.Command, Image: container.Image, Env: envVars, }, }, RestartPolicy: kapi.RestartPolicy{ Never: &kapi.RestartPolicyNever{}, }, }, } pod.Spec.Containers[0].ImagePullPolicy = kapi.PullIfNotPresent return pod, nil } // labelFor builds a string identifier for a DeploymentConfig. func labelForDeployment(deployment *kapi.ReplicationController) string { return fmt.Sprintf("%s/%s", deployment.Namespace, deployment.Name) } // statusFor gets the DeploymentStatus for deployment from its annotations. func statusFor(deployment *kapi.ReplicationController) deployapi.DeploymentStatus { return deployapi.DeploymentStatus(deployment.Annotations[deployapi.DeploymentStatusAnnotation]) } // DeploymentContainerCreator knows how to create a deployment pod's container based on // the deployment's strategy. type DeploymentContainerCreator interface { CreateContainer(*deployapi.DeploymentStrategy) *kapi.Container } // DeploymentControllerDeploymentClient abstracts access to deployments. type DeploymentControllerDeploymentClient interface { GetDeployment(namespace, name string) (*kapi.ReplicationController, error) UpdateDeployment(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) } // DeploymentControllerPodClient abstracts access to pods. type DeploymentControllerPodClient interface { CreatePod(namespace string, pod *kapi.Pod) (*kapi.Pod, error) DeletePod(namespace, name string) error } // DeploymentContainerCreatorImpl is a pluggable DeploymentContainerCreator. type DeploymentContainerCreatorImpl struct { CreateContainerFunc func(*deployapi.DeploymentStrategy) *kapi.Container } func (i *DeploymentContainerCreatorImpl) CreateContainer(strategy *deployapi.DeploymentStrategy) *kapi.Container { return i.CreateContainerFunc(strategy) } // DeploymentControllerDeploymentClientImpl is a pluggable deploymentControllerDeploymentClient. type DeploymentControllerDeploymentClientImpl struct { GetDeploymentFunc func(namespace, name string) (*kapi.ReplicationController, error) UpdateDeploymentFunc func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) } func (i *DeploymentControllerDeploymentClientImpl) GetDeployment(namespace, name string) (*kapi.ReplicationController, error) { return i.GetDeploymentFunc(namespace, name) } func (i *DeploymentControllerDeploymentClientImpl) UpdateDeployment(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { return i.UpdateDeploymentFunc(namespace, deployment) } // deploymentControllerPodClientImpl is a pluggable deploymentControllerPodClient. type DeploymentControllerPodClientImpl struct { CreatePodFunc func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) DeletePodFunc func(namespace, name string) error } func (i *DeploymentControllerPodClientImpl) CreatePod(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { return i.CreatePodFunc(namespace, pod) } func (i *DeploymentControllerPodClientImpl) DeletePod(namespace, name string) error { return i.DeletePodFunc(namespace, name) }