diff --git a/pkg/controller/deployment/deployment_controller.go b/pkg/controller/deployment/deployment_controller.go index 5120863e6ee..05b6698c329 100644 --- a/pkg/controller/deployment/deployment_controller.go +++ b/pkg/controller/deployment/deployment_controller.go @@ -18,7 +18,6 @@ package deployment import ( "fmt" - "hash/adler32" "math" "time" @@ -28,6 +27,7 @@ import ( "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/util" + deploymentUtil "k8s.io/kubernetes/pkg/util/deployment" ) type DeploymentController struct { @@ -116,59 +116,21 @@ func (d *DeploymentController) reconcileRollingUpdateDeployment(deployment exper } func (d *DeploymentController) getOldRCs(deployment experimental.Deployment) ([]*api.ReplicationController, error) { - namespace := deployment.ObjectMeta.Namespace - // 1. Find all pods whose labels match deployment.Spec.Selector - podList, err := d.client.Pods(namespace).List(labels.SelectorFromSet(deployment.Spec.Selector), fields.Everything()) - if err != nil { - return nil, fmt.Errorf("error listing pods: %v", err) - } - // 2. Find the corresponding RCs for pods in podList. - // TODO: Right now we list all RCs and then filter. We should add an API for this. - oldRCs := map[string]api.ReplicationController{} - rcList, err := d.client.ReplicationControllers(namespace).List(labels.Everything()) - if err != nil { - return nil, fmt.Errorf("error listing replication controllers: %v", err) - } - for _, pod := range podList.Items { - podLabelsSelector := labels.Set(pod.ObjectMeta.Labels) - for _, rc := range rcList.Items { - rcLabelsSelector := labels.SelectorFromSet(rc.Spec.Selector) - if rcLabelsSelector.Matches(podLabelsSelector) { - // Filter out RC that has the same pod template spec as the deployment - that is the new RC. - if api.Semantic.DeepEqual(rc.Spec.Template, getNewRCTemplate(deployment)) { - continue - } - oldRCs[rc.ObjectMeta.Name] = rc - } - } - } - requiredRCs := []*api.ReplicationController{} - for _, value := range oldRCs { - requiredRCs = append(requiredRCs, &value) - } - return requiredRCs, nil + return deploymentUtil.GetOldRCs(deployment, d.client) } // Returns an RC that matches the intent of the given deployment. // It creates a new RC if required. func (d *DeploymentController) getNewRC(deployment experimental.Deployment) (*api.ReplicationController, error) { - namespace := deployment.ObjectMeta.Namespace - // Find if the required RC exists already. - rcList, err := d.client.ReplicationControllers(namespace).List(labels.Everything()) - if err != nil { - return nil, fmt.Errorf("error listing replication controllers: %v", err) - } - newRCTemplate := getNewRCTemplate(deployment) - - for _, rc := range rcList.Items { - if api.Semantic.DeepEqual(rc.Spec.Template, newRCTemplate) { - // This is the new RC. - return &rc, nil - } + existingNewRC, err := deploymentUtil.GetNewRC(deployment, d.client) + if err != nil || existingNewRC != nil { + return existingNewRC, err } // new RC does not exist, create one. - podTemplateSpecHash := getPodTemplateSpecHash(deployment.Spec.Template) + namespace := deployment.ObjectMeta.Namespace + podTemplateSpecHash := deploymentUtil.GetPodTemplateSpecHash(deployment.Spec.Template) rcName := fmt.Sprintf("deploymentrc-%d", podTemplateSpecHash) + newRCTemplate := deploymentUtil.GetNewRCTemplate(deployment) newRC := api.ReplicationController{ ObjectMeta: api.ObjectMeta{ Name: rcName, @@ -187,30 +149,6 @@ func (d *DeploymentController) getNewRC(deployment experimental.Deployment) (*ap return createdRC, nil } -func getNewRCTemplate(deployment experimental.Deployment) *api.PodTemplateSpec { - // newRC will have the same template as in deployment spec, plus a unique label in some cases. - newRCTemplate := &api.PodTemplateSpec{ - ObjectMeta: deployment.Spec.Template.ObjectMeta, - Spec: deployment.Spec.Template.Spec, - } - podTemplateSpecHash := getPodTemplateSpecHash(newRCTemplate) - if deployment.Spec.UniqueLabelKey != "" { - newLabels := map[string]string{} - for key, value := range deployment.Spec.Template.ObjectMeta.Labels { - newLabels[key] = value - } - newLabels[deployment.Spec.UniqueLabelKey] = fmt.Sprintf("%d", podTemplateSpecHash) - newRCTemplate.ObjectMeta.Labels = newLabels - } - return newRCTemplate -} - -func getPodTemplateSpecHash(template *api.PodTemplateSpec) uint32 { - podTemplateSpecHasher := adler32.New() - util.DeepHashObject(podTemplateSpecHasher, template) - return podTemplateSpecHasher.Sum32() -} - func (d *DeploymentController) getPodsForRCs(replicationControllers []*api.ReplicationController) ([]api.Pod, error) { allPods := []api.Pod{} for _, rc := range replicationControllers { diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index b64b05839ce..4c5489024b3 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -35,6 +35,7 @@ import ( qosutil "k8s.io/kubernetes/pkg/kubelet/qos/util" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/types" + deploymentUtil "k8s.io/kubernetes/pkg/util/deployment" "k8s.io/kubernetes/pkg/util/sets" ) @@ -88,6 +89,7 @@ func expDescriberMap(c *client.Client) map[string]Describer { "HorizontalPodAutoscaler": &HorizontalPodAutoscalerDescriber{c}, "DaemonSet": &DaemonSetDescriber{c}, "Job": &JobDescriber{c}, + "Deployment": &DeploymentDescriber{c}, } } @@ -478,7 +480,11 @@ func describePod(pod *api.Pod, rcs []api.ReplicationController, events *api.Even fmt.Fprintf(out, "Reason:\t%s\n", pod.Status.Reason) fmt.Fprintf(out, "Message:\t%s\n", pod.Status.Message) fmt.Fprintf(out, "IP:\t%s\n", pod.Status.PodIP) - fmt.Fprintf(out, "Replication Controllers:\t%s\n", printReplicationControllersByLabels(rcs)) + var matchingRCs []*api.ReplicationController + for _, rc := range rcs { + matchingRCs = append(matchingRCs, &rc) + } + fmt.Fprintf(out, "Replication Controllers:\t%s\n", printReplicationControllersByLabels(matchingRCs)) fmt.Fprintf(out, "Containers:\n") describeContainers(pod, out) if len(pod.Status.Conditions) > 0 { @@ -1395,6 +1401,44 @@ func DescribeEvents(el *api.EventList, w io.Writer) { } } +// DeploymentDescriber generates information about a deployment. +type DeploymentDescriber struct { + client.Interface +} + +func (dd *DeploymentDescriber) Describe(namespace, name string) (string, error) { + d, err := dd.Experimental().Deployments(namespace).Get(name) + if err != nil { + return "", err + } + return tabbedString(func(out io.Writer) error { + fmt.Fprintf(out, "Name:\t%s\n", d.ObjectMeta.Name) + fmt.Fprintf(out, "Namespace:\t%s\n", d.ObjectMeta.Namespace) + fmt.Fprintf(out, "CreationTimestamp:\t%s\n", d.CreationTimestamp.Time.Format(time.RFC1123Z)) + fmt.Fprintf(out, "Labels:\t%s\n", labels.FormatLabels(d.Labels)) + fmt.Fprintf(out, "Selector:\t%s\n", labels.FormatLabels(d.Spec.Selector)) + fmt.Fprintf(out, "Replicas:\t%d updated / %d total\n", d.Status.UpdatedReplicas, d.Spec.Replicas) + fmt.Fprintf(out, "StrategyType:\t%s\n", d.Spec.Strategy.Type) + if d.Spec.Strategy.RollingUpdate != nil { + ru := d.Spec.Strategy.RollingUpdate + fmt.Fprintf(out, "RollingUpdateStrategy:\t%s max unavailable, %s max surge, %d min ready seconds\n", ru.MaxUnavailable.String(), ru.MaxSurge.String(), ru.MinReadySeconds) + } + oldRCs, err := deploymentUtil.GetOldRCs(*d, dd) + if err == nil { + fmt.Fprintf(out, "OldReplicationControllers:\t%s\n", printReplicationControllersByLabels(oldRCs)) + } + newRC, err := deploymentUtil.GetNewRC(*d, dd) + if err == nil { + var newRCs []*api.ReplicationController + if newRC != nil { + newRCs = append(newRCs, newRC) + } + fmt.Fprintf(out, "NewReplicationController:\t%s\n", printReplicationControllersByLabels(newRCs)) + } + return nil + }) +} + // Get all daemon set whose selectors would match a given set of labels. // TODO: Move this to pkg/client and ideally implement it server-side (instead // of getting all DS's and searching through them manually). @@ -1442,7 +1486,7 @@ func getReplicationControllersForLabels(c client.ReplicationControllerInterface, return matchingRCs, nil } -func printReplicationControllersByLabels(matchingRCs []api.ReplicationController) string { +func printReplicationControllersByLabels(matchingRCs []*api.ReplicationController) string { // Format the matching RC's into strings. var rcStrings []string for _, controller := range matchingRCs { diff --git a/pkg/kubectl/describe_test.go b/pkg/kubectl/describe_test.go index eadc740640d..dedc3089592 100644 --- a/pkg/kubectl/describe_test.go +++ b/pkg/kubectl/describe_test.go @@ -27,6 +27,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/apis/experimental" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/testclient" ) @@ -476,6 +477,26 @@ func TestPersistentVolumeDescriber(t *testing.T) { if str == "" { t.Errorf("Unexpected empty string for test %s. Expected PV Describer output", name) } - + } +} + +func TestDescribeDeployment(t *testing.T) { + fake := testclient.NewSimpleFake(&experimental.Deployment{ + ObjectMeta: api.ObjectMeta{ + Name: "bar", + Namespace: "foo", + }, + Spec: experimental.DeploymentSpec{ + Template: &api.PodTemplateSpec{}, + }, + }) + c := &describeClient{T: t, Namespace: "foo", Interface: fake} + d := DeploymentDescriber{c} + out, err := d.Describe("foo", "bar") + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if !strings.Contains(out, "bar") || !strings.Contains(out, "foo") { + t.Errorf("unexpected out: %s", out) } } diff --git a/pkg/util/deployment/deployment.go b/pkg/util/deployment/deployment.go new file mode 100644 index 00000000000..99c87390289 --- /dev/null +++ b/pkg/util/deployment/deployment.go @@ -0,0 +1,109 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 deployment + +import ( + "fmt" + "hash/adler32" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/experimental" + client "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/util" +) + +// Returns the old RCs targetted by the given Deployment. +func GetOldRCs(deployment experimental.Deployment, c client.Interface) ([]*api.ReplicationController, error) { + namespace := deployment.ObjectMeta.Namespace + // 1. Find all pods whose labels match deployment.Spec.Selector + podList, err := c.Pods(namespace).List(labels.SelectorFromSet(deployment.Spec.Selector), fields.Everything()) + if err != nil { + return nil, fmt.Errorf("error listing pods: %v", err) + } + // 2. Find the corresponding RCs for pods in podList. + // TODO: Right now we list all RCs and then filter. We should add an API for this. + oldRCs := map[string]api.ReplicationController{} + rcList, err := c.ReplicationControllers(namespace).List(labels.Everything()) + if err != nil { + return nil, fmt.Errorf("error listing replication controllers: %v", err) + } + for _, pod := range podList.Items { + podLabelsSelector := labels.Set(pod.ObjectMeta.Labels) + for _, rc := range rcList.Items { + rcLabelsSelector := labels.SelectorFromSet(rc.Spec.Selector) + if rcLabelsSelector.Matches(podLabelsSelector) { + // Filter out RC that has the same pod template spec as the deployment - that is the new RC. + if api.Semantic.DeepEqual(rc.Spec.Template, GetNewRCTemplate(deployment)) { + continue + } + oldRCs[rc.ObjectMeta.Name] = rc + } + } + } + requiredRCs := []*api.ReplicationController{} + for _, value := range oldRCs { + requiredRCs = append(requiredRCs, &value) + } + return requiredRCs, nil +} + +// Returns an RC that matches the intent of the given deployment. +// Returns nil if the new RC doesnt exist yet. +func GetNewRC(deployment experimental.Deployment, c client.Interface) (*api.ReplicationController, error) { + namespace := deployment.ObjectMeta.Namespace + rcList, err := c.ReplicationControllers(namespace).List(labels.Everything()) + if err != nil { + return nil, fmt.Errorf("error listing replication controllers: %v", err) + } + newRCTemplate := GetNewRCTemplate(deployment) + + for _, rc := range rcList.Items { + if api.Semantic.DeepEqual(rc.Spec.Template, newRCTemplate) { + // This is the new RC. + return &rc, nil + } + } + // new RC does not exist. + return nil, nil +} + +// Returns the desired PodTemplateSpec for the new RC corresponding to the given RC. +func GetNewRCTemplate(deployment experimental.Deployment) *api.PodTemplateSpec { + // newRC will have the same template as in deployment spec, plus a unique label in some cases. + newRCTemplate := &api.PodTemplateSpec{ + ObjectMeta: deployment.Spec.Template.ObjectMeta, + Spec: deployment.Spec.Template.Spec, + } + podTemplateSpecHash := GetPodTemplateSpecHash(newRCTemplate) + if deployment.Spec.UniqueLabelKey != "" { + newLabels := map[string]string{} + for key, value := range deployment.Spec.Template.ObjectMeta.Labels { + newLabels[key] = value + } + newLabels[deployment.Spec.UniqueLabelKey] = fmt.Sprintf("%d", podTemplateSpecHash) + newRCTemplate.ObjectMeta.Labels = newLabels + } + return newRCTemplate +} + +func GetPodTemplateSpecHash(template *api.PodTemplateSpec) uint32 { + podTemplateSpecHasher := adler32.New() + util.DeepHashObject(podTemplateSpecHasher, template) + return podTemplateSpecHasher.Sum32() +}