// Describe returns the description of a DeploymentConfig func (d *DeploymentConfigDescriber) Describe(namespace, name string) (string, error) { deploymentConfig, err := d.client.getDeploymentConfig(namespace, name) if err != nil { return "", err } events, err := d.client.listEvents(deploymentConfig) if err != nil { return "", err } return tabbedString(func(out *tabwriter.Writer) error { formatMeta(out, deploymentConfig.ObjectMeta) if deploymentConfig.Status.LatestVersion == 0 { formatString(out, "Latest Version", "Not deployed") } else { formatString(out, "Latest Version", strconv.Itoa(deploymentConfig.Status.LatestVersion)) } printTriggers(deploymentConfig.Spec.Triggers, out) formatString(out, "Strategy", deploymentConfig.Spec.Strategy.Type) printStrategy(deploymentConfig.Spec.Strategy, out) printDeploymentConfigSpec(deploymentConfig.Spec, out) if deploymentConfig.Status.Details != nil && len(deploymentConfig.Status.Details.Message) > 0 { fmt.Fprintf(out, "Warning:\t%s\n", deploymentConfig.Status.Details.Message) } deploymentName := deployutil.LatestDeploymentNameForConfig(deploymentConfig) deployment, err := d.client.getDeployment(namespace, deploymentName) if err != nil { if kerrors.IsNotFound(err) { formatString(out, "Latest Deployment", "<none>") } else { formatString(out, "Latest Deployment", fmt.Sprintf("error: %v", err)) } } else { header := fmt.Sprintf("Deployment #%d (latest)", deployutil.DeploymentVersionFor(deployment)) printDeploymentRc(deployment, d.client, out, header, true) } deploymentsHistory, err := d.client.listDeployments(namespace, labels.Everything()) if err == nil { sorted := rcSorter{} sorted = append(sorted, deploymentsHistory.Items...) sort.Sort(sorted) for _, item := range sorted { if item.Name != deploymentName && deploymentConfig.Name == deployutil.DeploymentConfigNameFor(&item) { header := fmt.Sprintf("Deployment #%d", deployutil.DeploymentVersionFor(&item)) printDeploymentRc(&item, d.client, out, header, false) } } } if events != nil { kctl.DescribeEvents(events, out) } return nil }) }
// ViewHistory returns a description of all the history it can find for a deployment config. func (h *DeploymentConfigHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) { opts := kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(name)} deploymentList, err := h.rn.ReplicationControllers(namespace).List(opts) if err != nil { return "", err } if len(deploymentList.Items) == 0 { return "No rollout history found.", nil } items := deploymentList.Items history := make([]*kapi.ReplicationController, 0, len(items)) for i := range items { history = append(history, &items[i]) } // Print details of a specific revision if revision > 0 { var desired *kapi.PodTemplateSpec // We could use a binary search here but brute-force is always faster to write for i := range history { rc := history[i] if deployutil.DeploymentVersionFor(rc) == revision { desired = rc.Spec.Template break } } if desired == nil { return "", fmt.Errorf("unable to find the specified revision") } buf := bytes.NewBuffer([]byte{}) kubectl.DescribePodTemplate(desired, buf) return buf.String(), nil } sort.Sort(deployutil.ByLatestVersionAsc(history)) return tabbedString(func(out *tabwriter.Writer) error { fmt.Fprintf(out, "REVISION\tSTATUS\tCAUSE\n") for i := range history { rc := history[i] rev := deployutil.DeploymentVersionFor(rc) status := deployutil.DeploymentStatusFor(rc) cause := rc.Annotations[deployapi.DeploymentStatusReasonAnnotation] if len(cause) == 0 { cause = "<unknown>" } fmt.Fprintf(out, "%d\t%s\t%s\n", rev, status, cause) } return nil }) }
// cancel cancels any deployment process in progress for config. func (o DeployOptions) cancel(config *deployapi.DeploymentConfig) error { if config.Spec.Paused { return fmt.Errorf("cannot cancel a paused deployment config") } deployments, err := o.kubeClient.ReplicationControllers(config.Namespace).List(kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(config.Name)}) if err != nil { return err } if len(deployments.Items) == 0 { fmt.Fprintf(o.out, "There have been no deployments for %s/%s\n", config.Namespace, config.Name) return nil } sort.Sort(deployutil.ByLatestVersionDesc(deployments.Items)) failedCancellations := []string{} anyCancelled := false for _, deployment := range deployments.Items { status := deployutil.DeploymentStatusFor(&deployment) switch status { case deployapi.DeploymentStatusNew, deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning: if deployutil.IsDeploymentCancelled(&deployment) { continue } deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledByUser _, err := o.kubeClient.ReplicationControllers(deployment.Namespace).Update(&deployment) if err == nil { fmt.Fprintf(o.out, "Cancelled deployment #%d\n", config.Status.LatestVersion) anyCancelled = true } else { fmt.Fprintf(o.out, "Couldn't cancel deployment #%d (status: %s): %v\n", deployutil.DeploymentVersionFor(&deployment), status, err) failedCancellations = append(failedCancellations, strconv.FormatInt(deployutil.DeploymentVersionFor(&deployment), 10)) } } } if len(failedCancellations) > 0 { return fmt.Errorf("couldn't cancel deployment %s", strings.Join(failedCancellations, ", ")) } if !anyCancelled { latest := &deployments.Items[0] maybeCancelling := "" if deployutil.IsDeploymentCancelled(latest) && !deployutil.IsTerminatedDeployment(latest) { maybeCancelling = " (cancelling)" } timeAt := strings.ToLower(units.HumanDuration(time.Now().Sub(latest.CreationTimestamp.Time))) fmt.Fprintf(o.out, "No deployments are in progress (latest deployment #%d %s%s %s ago)\n", deployutil.DeploymentVersionFor(latest), strings.ToLower(string(deployutil.DeploymentStatusFor(latest))), maybeCancelling, timeAt) } return nil }
func deploymentReachedCompletion(dc *deployapi.DeploymentConfig, rcs []kapi.ReplicationController, pods []kapi.Pod) (bool, error) { if len(rcs) == 0 { return false, nil } rc := rcs[len(rcs)-1] version := deployutil.DeploymentVersionFor(&rc) if version != dc.Status.LatestVersion { return false, nil } if !deployutil.IsCompleteDeployment(&rc) { return false, nil } cond := deployutil.GetDeploymentCondition(dc.Status, deployapi.DeploymentProgressing) if cond == nil || cond.Reason != deployutil.NewRcAvailableReason { return false, nil } expectedReplicas := dc.Spec.Replicas if dc.Spec.Test { expectedReplicas = 0 } if rc.Spec.Replicas != int32(expectedReplicas) { return false, fmt.Errorf("deployment is complete but doesn't have expected spec replicas: %d %d", rc.Spec.Replicas, expectedReplicas) } if rc.Status.Replicas != int32(expectedReplicas) { e2e.Logf("POSSIBLE_ANOMALY: deployment is complete but doesn't have expected status replicas: %d %d", rc.Status.Replicas, expectedReplicas) return false, nil } e2e.Logf("Latest rollout of dc/%s (rc/%s) is complete.", dc.Name, rc.Name) return true, nil }
func deploymentReachedCompletion(dc *deployapi.DeploymentConfig, rcs []kapi.ReplicationController, pods []kapi.Pod) (bool, error) { if len(rcs) == 0 { return false, nil } rc := rcs[len(rcs)-1] version := deployutil.DeploymentVersionFor(&rc) if version != dc.Status.LatestVersion { return false, nil } status := rc.Annotations[deployapi.DeploymentStatusAnnotation] if deployapi.DeploymentStatus(status) != deployapi.DeploymentStatusComplete { return false, nil } expectedReplicas := dc.Spec.Replicas if dc.Spec.Test { expectedReplicas = 0 } if rc.Spec.Replicas != int32(expectedReplicas) { return false, fmt.Errorf("deployment is complete but doesn't have expected spec replicas: %d %d", rc.Spec.Replicas, expectedReplicas) } if rc.Status.Replicas != int32(expectedReplicas) { e2e.Logf("POSSIBLE_ANOMALY: deployment is complete but doesn't have expected status replicas: %d %d", rc.Status.Replicas, expectedReplicas) return false, nil } e2e.Logf("Latest rollout of dc/%s (rc/%s) is complete.", dc.Name, rc.Name) return true, nil }
func deploymentRunning(dc *deployapi.DeploymentConfig, rcs []kapi.ReplicationController, pods []kapi.Pod) (bool, error) { if len(rcs) == 0 { return false, nil } rc := rcs[len(rcs)-1] version := deployutil.DeploymentVersionFor(&rc) if version != dc.Status.LatestVersion { //e2e.Logf("deployment %s is not the latest version on DC: %d", rc.Name, version) return false, nil } status := rc.Annotations[deployapi.DeploymentStatusAnnotation] switch deployapi.DeploymentStatus(status) { case deployapi.DeploymentStatusFailed: if deployutil.IsDeploymentCancelled(&rc) { return true, nil } reason := deployutil.DeploymentStatusReasonFor(&rc) if reason == "deployer pod no longer exists" { return true, nil } return false, fmt.Errorf("deployment failed: %v", deployutil.DeploymentStatusReasonFor(&rc)) case deployapi.DeploymentStatusRunning, deployapi.DeploymentStatusComplete: return true, nil default: return false, nil } }
// findTargetDeployment finds the deployment which is the rollback target by // searching for deployments associated with config. If desiredVersion is >0, // the deployment matching desiredVersion will be returned. If desiredVersion // is <=0, the last completed deployment which is older than the config's // version will be returned. func (o *RollbackOptions) findTargetDeployment(config *deployapi.DeploymentConfig, desiredVersion int64) (*kapi.ReplicationController, error) { // Find deployments for the config sorted by version descending. deployments, err := o.kc.ReplicationControllers(config.Namespace).List(kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(config.Name)}) if err != nil { return nil, err } sort.Sort(deployutil.ByLatestVersionDesc(deployments.Items)) // Find the target deployment for rollback. If a version was specified, // use the version for a search. Otherwise, use the last completed // deployment. var target *kapi.ReplicationController for _, deployment := range deployments.Items { version := deployutil.DeploymentVersionFor(&deployment) if desiredVersion > 0 { if version == desiredVersion { target = &deployment break } } else { if version < config.Status.LatestVersion && deployutil.DeploymentStatusFor(&deployment) == deployapi.DeploymentStatusComplete { target = &deployment break } } } if target == nil { return nil, fmt.Errorf("couldn't find deployment for rollback") } return target, nil }
func describeDeploymentStatus(deploy *kapi.ReplicationController, first, test bool) string { timeAt := strings.ToLower(formatRelativeTime(deploy.CreationTimestamp.Time)) status := deployutil.DeploymentStatusFor(deploy) version := deployutil.DeploymentVersionFor(deploy) maybeCancelling := "" if deployutil.IsDeploymentCancelled(deploy) && !deployutil.IsTerminatedDeployment(deploy) { maybeCancelling = " (cancelling)" } switch status { case deployapi.DeploymentStatusFailed: reason := deployutil.DeploymentStatusReasonFor(deploy) if len(reason) > 0 { reason = fmt.Sprintf(": %s", reason) } // TODO: encode fail time in the rc return fmt.Sprintf("deployment #%d failed %s ago%s%s", version, timeAt, reason, describePodSummaryInline(deploy, false)) case deployapi.DeploymentStatusComplete: // TODO: pod status output if test { return fmt.Sprintf("test deployment #%d deployed %s ago", version, timeAt) } return fmt.Sprintf("deployment #%d deployed %s ago%s", version, timeAt, describePodSummaryInline(deploy, first)) case deployapi.DeploymentStatusRunning: format := "deployment #%d running%s for %s%s" if test { format = "test deployment #%d running%s for %s%s" } return fmt.Sprintf(format, version, maybeCancelling, timeAt, describePodSummaryInline(deploy, false)) default: return fmt.Sprintf("deployment #%d %s%s %s ago%s", version, strings.ToLower(string(status)), maybeCancelling, timeAt, describePodSummaryInline(deploy, false)) } }
func TestTriggers_manual(t *testing.T) { testutil.DeleteAllEtcdKeys() openshift := NewTestDeployOpenshift(t) defer openshift.Close() config := deploytest.OkDeploymentConfig(0) config.Namespace = testutil.Namespace() config.Triggers = []deployapi.DeploymentTriggerPolicy{ { Type: deployapi.DeploymentTriggerManual, }, } dc, err := openshift.Client.DeploymentConfigs(testutil.Namespace()).Create(config) if err != nil { t.Fatalf("Couldn't create DeploymentConfig: %v %#v", err, config) } watch, err := openshift.KubeClient.ReplicationControllers(testutil.Namespace()).Watch(labels.Everything(), fields.Everything(), dc.ResourceVersion) if err != nil { t.Fatalf("Couldn't subscribe to Deployments: %v", err) } defer watch.Stop() retryErr := kclient.RetryOnConflict(wait.Backoff{Steps: maxUpdateRetries}, func() error { config, err := openshift.Client.DeploymentConfigs(testutil.Namespace()).Generate(config.Name) if err != nil { return err } if config.LatestVersion != 1 { t.Fatalf("Generated deployment should have version 1: %#v", config) } t.Logf("config(1): %#v", config) updatedConfig, err := openshift.Client.DeploymentConfigs(testutil.Namespace()).Update(config) if err != nil { return err } t.Logf("config(2): %#v", updatedConfig) return nil }) if retryErr != nil { t.Fatal(err) } event := <-watch.ResultChan() if e, a := watchapi.Added, event.Type; e != a { t.Fatalf("expected watch event type %s, got %s", e, a) } deployment := event.Object.(*kapi.ReplicationController) if e, a := config.Name, deployutil.DeploymentConfigNameFor(deployment); e != a { t.Fatalf("Expected deployment annotated with deploymentConfig '%s', got '%s'", e, a) } if e, a := 1, deployutil.DeploymentVersionFor(deployment); e != a { t.Fatalf("Deployment annotation version does not match: %#v", deployment) } }
// cancel cancels any deployment process in progress for config. func (o *DeployOptions) cancel(config *deployapi.DeploymentConfig, out io.Writer) error { deployments, err := o.kubeClient.ReplicationControllers(config.Namespace).List(deployutil.ConfigSelector(config.Name)) if err != nil { return err } if len(deployments.Items) == 0 { fmt.Fprintln(out, "no deployments found to cancel") return nil } failedCancellations := []string{} anyCancelled := false for _, deployment := range deployments.Items { status := deployutil.DeploymentStatusFor(&deployment) switch status { case deployapi.DeploymentStatusNew, deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning: if deployutil.IsDeploymentCancelled(&deployment) { continue } deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledByUser _, err := o.kubeClient.ReplicationControllers(deployment.Namespace).Update(&deployment) if err == nil { fmt.Fprintf(out, "cancelled deployment #%d\n", config.LatestVersion) anyCancelled = true } else { fmt.Fprintf(out, "couldn't cancel deployment #%d (status: %s): %v\n", deployutil.DeploymentVersionFor(&deployment), status, err) failedCancellations = append(failedCancellations, strconv.Itoa(deployutil.DeploymentVersionFor(&deployment))) } } } if len(failedCancellations) > 0 { return fmt.Errorf("couldn't cancel deployment %s", strings.Join(failedCancellations, ", ")) } if !anyCancelled { fmt.Fprintln(out, "no active deployments to cancel") } return nil }
func deploymentFailed(dc *deployapi.DeploymentConfig, rcs []kapi.ReplicationController, _ []kapi.Pod) (bool, error) { if len(rcs) == 0 { return false, nil } rc := rcs[len(rcs)-1] version := deployutil.DeploymentVersionFor(&rc) if version != dc.Status.LatestVersion { return false, nil } return deployutil.IsFailedDeployment(&rc), nil }
// cancel cancels any deployment process in progress for config. func (c *cancelDeploymentCommand) cancel(config *deployapi.DeploymentConfig, out io.Writer) error { deployments, err := c.client.ListDeploymentsForConfig(config.Namespace, config.Name) if err != nil { return err } if len(deployments.Items) == 0 { fmt.Fprintln(out, "no deployments found to cancel") return nil } failedCancellations := []string{} for _, deployment := range deployments.Items { status := deployutil.DeploymentStatusFor(&deployment) switch status { case deployapi.DeploymentStatusNew, deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning: if deployutil.IsDeploymentCancelled(&deployment) { continue } deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledByUser _, err := c.client.UpdateDeployment(&deployment) if err == nil { fmt.Fprintf(out, "cancelled #%d\n", config.LatestVersion) } else { fmt.Fprintf(out, "couldn't cancel deployment %d (status: %s): %v\n", deployutil.DeploymentVersionFor(&deployment), status, err) failedCancellations = append(failedCancellations, strconv.Itoa(deployutil.DeploymentVersionFor(&deployment))) } default: fmt.Fprintln(out, "no active deployments to cancel") } } if len(failedCancellations) == 0 { return nil } else { return fmt.Errorf("couldn't cancel deployment %s", strings.Join(failedCancellations, ", ")) } }
func deploymentFailed(dc *deployapi.DeploymentConfig, rcs []kapi.ReplicationController, _ []kapi.Pod) (bool, error) { if len(rcs) == 0 { return false, nil } rc := rcs[len(rcs)-1] version := deployutil.DeploymentVersionFor(&rc) if version != dc.Status.LatestVersion { return false, nil } if !deployutil.IsFailedDeployment(&rc) { return false, nil } cond := deployutil.GetDeploymentCondition(dc.Status, deployapi.DeploymentProgressing) return cond != nil && cond.Reason == deployutil.TimedOutReason, nil }
// TODO kill this. It should be based on an edge traversal to loaded replication controllers func JoinDeployments(node *deploygraph.DeploymentConfigNode, deploys []kapi.ReplicationController) { matches := []*kapi.ReplicationController{} for i := range deploys { if belongsToDeploymentConfig(node.DeploymentConfig, &deploys[i]) { matches = append(matches, &deploys[i]) } } if len(matches) == 0 { return } sort.Sort(RecentDeploymentReferences(matches)) if node.DeploymentConfig.LatestVersion == deployutil.DeploymentVersionFor(matches[0]) { node.ActiveDeployment = matches[0] node.Deployments = matches[1:] return } node.Deployments = matches }
// RelevantDeployments returns the active deployment and a list of inactive deployments (in order from newest to oldest) func RelevantDeployments(g osgraph.Graph, dcNode *deploygraph.DeploymentConfigNode) (*kubegraph.ReplicationControllerNode, []*kubegraph.ReplicationControllerNode) { allDeployments := []*kubegraph.ReplicationControllerNode{} uncastDeployments := g.SuccessorNodesByEdgeKind(dcNode, DeploymentEdgeKind) if len(uncastDeployments) == 0 { return nil, []*kubegraph.ReplicationControllerNode{} } for i := range uncastDeployments { allDeployments = append(allDeployments, uncastDeployments[i].(*kubegraph.ReplicationControllerNode)) } sort.Sort(RecentDeploymentReferences(allDeployments)) if dcNode.DeploymentConfig.Status.LatestVersion == deployutil.DeploymentVersionFor(allDeployments[0]) { return allDeployments[0], allDeployments[1:] } return nil, allDeployments }
func describeDeploymentStatus(deploy *kapi.ReplicationController, first bool) string { timeAt := strings.ToLower(formatRelativeTime(deploy.CreationTimestamp.Time)) status := deployutil.DeploymentStatusFor(deploy) version := deployutil.DeploymentVersionFor(deploy) switch status { case deployapi.DeploymentStatusFailed: reason := deployutil.DeploymentStatusReasonFor(deploy) if len(reason) > 0 { reason = fmt.Sprintf(": %s", reason) } // TODO: encode fail time in the rc return fmt.Sprintf("#%d deployment failed %s ago%s%s", version, timeAt, reason, describePodSummaryInline(deploy, false)) case deployapi.DeploymentStatusComplete: // TODO: pod status output return fmt.Sprintf("#%d deployed %s ago%s", version, timeAt, describePodSummaryInline(deploy, first)) case deployapi.DeploymentStatusRunning: return fmt.Sprintf("#%d deployment running for %s%s", version, timeAt, describePodSummaryInline(deploy, false)) default: return fmt.Sprintf("#%d deployment %s %s ago%s", version, strings.ToLower(string(status)), timeAt, describePodSummaryInline(deploy, false)) } }
func TestRollbackOptions_findTargetDeployment(t *testing.T) { type existingDeployment struct { version int status deployapi.DeploymentStatus } tests := []struct { name string configVersion int desiredVersion int existing []existingDeployment expectedVersion int errorExpected bool }{ { name: "desired found", configVersion: 3, existing: []existingDeployment{ {1, deployapi.DeploymentStatusComplete}, {2, deployapi.DeploymentStatusComplete}, {3, deployapi.DeploymentStatusComplete}, }, desiredVersion: 1, expectedVersion: 1, errorExpected: false, }, { name: "desired not found", configVersion: 3, existing: []existingDeployment{ {2, deployapi.DeploymentStatusComplete}, {3, deployapi.DeploymentStatusComplete}, }, desiredVersion: 1, errorExpected: true, }, { name: "desired not supplied, target found", configVersion: 3, existing: []existingDeployment{ {1, deployapi.DeploymentStatusComplete}, {2, deployapi.DeploymentStatusFailed}, {3, deployapi.DeploymentStatusComplete}, }, desiredVersion: 0, expectedVersion: 1, errorExpected: false, }, { name: "desired not supplied, target not found", configVersion: 3, existing: []existingDeployment{ {1, deployapi.DeploymentStatusFailed}, {2, deployapi.DeploymentStatusFailed}, {3, deployapi.DeploymentStatusComplete}, }, desiredVersion: 0, errorExpected: true, }, } for _, test := range tests { t.Logf("evaluating test: %s", test.name) existingControllers := &kapi.ReplicationControllerList{} for _, existing := range test.existing { config := deploytest.OkDeploymentConfig(existing.version) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(existing.status) existingControllers.Items = append(existingControllers.Items, *deployment) } fakekc := ktc.NewSimpleFake(existingControllers) opts := &RollbackOptions{ kc: fakekc, } config := deploytest.OkDeploymentConfig(test.configVersion) target, err := opts.findTargetDeployment(config, test.desiredVersion) if err != nil { if !test.errorExpected { t.Fatalf("unexpected error: %s", err) } continue } else { if test.errorExpected && err == nil { t.Fatalf("expected an error") } } if target == nil { t.Fatalf("expected a target deployment") } if e, a := test.expectedVersion, deployutil.DeploymentVersionFor(target); e != a { t.Errorf("expected target version %d, got %d", e, a) } } }
"the controller needs to have synced with the updated deployment configuration before checking that the revision history limits are being adhered to") deploymentConfig, deployments, _, err := deploymentInfo(oc, "history-limit") o.Expect(err).NotTo(o.HaveOccurred()) // sanity check to ensure that the following asertion on the amount of old deployments is valid o.Expect(*deploymentConfig.Spec.RevisionHistoryLimit).To(o.Equal(int32(revisionHistoryLimit))) // we need to filter out any deployments that we don't care about, // namely the active deployment and any newer deployments oldDeployments := deployutil.DeploymentsForCleanup(deploymentConfig, deployments) // we should not have more deployments than acceptable o.Expect(len(oldDeployments)).To(o.BeNumerically("==", revisionHistoryLimit)) // the deployments we continue to keep should be the latest ones for _, deployment := range oldDeployments { o.Expect(deployutil.DeploymentVersionFor(&deployment)).To(o.BeNumerically(">=", iterations-revisionHistoryLimit)) } }) }) g.Describe("with minimum ready seconds set", func() { g.AfterEach(func() { failureTrap(oc, "minreadytest", g.CurrentGinkgoTestDescription().Failed) }) g.It("should not transition the deployment to Complete before satisfied [Conformance]", func() { _, name, err := createFixture(oc, minReadySecondsFixture) o.Expect(err).NotTo(o.HaveOccurred()) g.By("verifying the deployment is marked running") o.Expect(waitForLatestCondition(oc, name, deploymentRunTimeout, deploymentRunning)).NotTo(o.HaveOccurred())
// TestHandle_cleanupDeploymentFailure ensures that clean up happens // for the deployment if the deployer pod fails. // - failed deployment is scaled down // - the last completed deployment is scaled back up func TestHandle_cleanupDeploymentFailure(t *testing.T) { var existingDeployments *kapi.ReplicationControllerList var failedDeployment *kapi.ReplicationController // map of deployment-version to updated replicas var updatedDeployments map[int]*kapi.ReplicationController controller := &DeployerPodController{ deploymentClient: &deploymentClientImpl{ getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { return failedDeployment, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { if _, found := updatedDeployments[deployutil.DeploymentVersionFor(deployment)]; found { t.Fatalf("unexpected multiple updates for deployment #%d", deployutil.DeploymentVersionFor(deployment)) } updatedDeployments[deployutil.DeploymentVersionFor(deployment)] = deployment return deployment, nil }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { return existingDeployments, nil }, }, } type existing struct { version int status deployapi.DeploymentStatus initialReplicas int updatedReplicas int } type scenario struct { name string // this is the deployment that is passed to Handle version int // this is the target replicas for the deployment that failed desiredReplicas int // existing deployments also include the one being handled currently existing []existing } // existing deployments intentionally placed un-ordered // in order to verify sorting scenarios := []scenario{ {"No previous deployments", 1, 3, []existing{ {1, deployapi.DeploymentStatusRunning, 3, 0}, }}, {"Multiple existing deployments - none in complete state", 3, 2, []existing{ {1, deployapi.DeploymentStatusFailed, 2, 2}, {2, deployapi.DeploymentStatusFailed, 0, 0}, {3, deployapi.DeploymentStatusRunning, 2, 0}, }}, {"Failed deployment is already at 0 replicas", 3, 2, []existing{ {1, deployapi.DeploymentStatusFailed, 2, 2}, {2, deployapi.DeploymentStatusFailed, 0, 0}, {3, deployapi.DeploymentStatusRunning, 0, 0}, }}, {"Multiple existing completed deployments", 4, 2, []existing{ {3, deployapi.DeploymentStatusComplete, 0, 2}, {2, deployapi.DeploymentStatusComplete, 0, 0}, {4, deployapi.DeploymentStatusRunning, 1, 0}, {1, deployapi.DeploymentStatusFailed, 0, 0}, }}, // A deployment already exists after the current failed deployment // only the current deployment is marked as failed // the completed deployment is not scaled up {"Deployment exists after current failed", 4, 2, []existing{ {3, deployapi.DeploymentStatusComplete, 1, 1}, {2, deployapi.DeploymentStatusComplete, 0, 0}, {4, deployapi.DeploymentStatusRunning, 2, 0}, {5, deployapi.DeploymentStatusNew, 0, 0}, {1, deployapi.DeploymentStatusFailed, 0, 0}, }}, } for _, scenario := range scenarios { t.Logf("running scenario: %s", scenario.name) updatedDeployments = make(map[int]*kapi.ReplicationController) failedDeployment = nil existingDeployments = &kapi.ReplicationControllerList{} for _, e := range scenario.existing { d, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(e.version), kapi.Codec) d.Annotations[deployapi.DeploymentStatusAnnotation] = string(e.status) d.Spec.Replicas = e.initialReplicas // if this is the deployment passed to Handle, set the desired replica annotation if e.version == scenario.version { d.Annotations[deployapi.DesiredReplicasAnnotation] = strconv.Itoa(scenario.desiredReplicas) failedDeployment = d } existingDeployments.Items = append(existingDeployments.Items, *d) } associatedDeployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(scenario.version), kapi.Codec) err := controller.Handle(terminatedPod(associatedDeployment)) if err != nil { t.Fatalf("unexpected error: %v", err) } // only the failed and the last completed deployment should be updated if len(updatedDeployments) > 2 { t.Fatalf("expected to update only the failed and last completed deployment") } for _, existing := range scenario.existing { updatedDeployment, ok := updatedDeployments[existing.version] if existing.initialReplicas != existing.updatedReplicas { if !ok { t.Fatalf("expected deployment #%d to be updated", existing.version) } if e, a := existing.updatedReplicas, updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected deployment #%d to be scaled to %d, got %d", existing.version, e, a) } } else if ok && existing.version != scenario.version { t.Fatalf("unexpected update for deployment #%d; replicas %d; status: %s", existing.version, updatedDeployment.Spec.Replicas, deployutil.DeploymentStatusFor(updatedDeployment)) } } if deployutil.DeploymentStatusFor(updatedDeployments[scenario.version]) != deployapi.DeploymentStatusFailed { t.Fatalf("status for deployment #%d expected to be updated to failed; got %s", scenario.version, deployutil.DeploymentStatusFor(updatedDeployments[scenario.version])) } if updatedDeployments[scenario.version].Spec.Replicas != 0 { t.Fatalf("deployment #%d expected to be scaled down to 0; got %d", scenario.version, updatedDeployments[scenario.version].Spec.Replicas) } } }
func TestDeployer_deployScenarios(t *testing.T) { mkd := func(version int64, status deployapi.DeploymentStatus, replicas int32, desired int32) *kapi.ReplicationController { deployment := mkdeployment(version, status) deployment.Spec.Replicas = int32(replicas) if desired > 0 { deployment.Annotations[deployapi.DesiredReplicasAnnotation] = strconv.Itoa(int(desired)) } return deployment } type scaleEvent struct { version int64 size int32 } scenarios := []struct { name string deployments []*kapi.ReplicationController fromVersion int64 toVersion int64 scaleEvents []scaleEvent }{ { "initial deployment", // existing deployments []*kapi.ReplicationController{ mkd(1, deployapi.DeploymentStatusNew, 0, 3), }, // from and to version 0, 1, // expected scale events []scaleEvent{}, }, { "last deploy failed", // existing deployments []*kapi.ReplicationController{ mkd(1, deployapi.DeploymentStatusComplete, 3, 0), mkd(2, deployapi.DeploymentStatusFailed, 1, 3), mkd(3, deployapi.DeploymentStatusNew, 0, 3), }, // from and to version 1, 3, // expected scale events []scaleEvent{ {2, 0}, }, }, { "sequential complete", // existing deployments []*kapi.ReplicationController{ mkd(1, deployapi.DeploymentStatusComplete, 0, 0), mkd(2, deployapi.DeploymentStatusComplete, 3, 0), mkd(3, deployapi.DeploymentStatusNew, 0, 3), }, // from and to version 2, 3, // expected scale events []scaleEvent{}, }, { "sequential failure", // existing deployments []*kapi.ReplicationController{ mkd(1, deployapi.DeploymentStatusFailed, 1, 3), mkd(2, deployapi.DeploymentStatusFailed, 1, 3), mkd(3, deployapi.DeploymentStatusNew, 0, 3), }, // from and to version 0, 3, // expected scale events []scaleEvent{ {1, 0}, {2, 0}, }, }, } for _, s := range scenarios { t.Logf("executing scenario %s", s.name) findDeployment := func(version int64) *kapi.ReplicationController { for _, d := range s.deployments { if deployutil.DeploymentVersionFor(d) == version { return d } } return nil } var actualFrom, actualTo *kapi.ReplicationController var actualDesired int32 to := findDeployment(s.toVersion) scaler := &scalertest.FakeScaler{} deployer := &Deployer{ out: &bytes.Buffer{}, errOut: &bytes.Buffer{}, strategyFor: func(config *deployapi.DeploymentConfig) (strategy.DeploymentStrategy, error) { return &testStrategy{ deployFunc: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int) error { actualFrom = from actualTo = to actualDesired = int32(desiredReplicas) return nil }, }, nil }, getDeployment: func(namespace, name string) (*kapi.ReplicationController, error) { return to, nil }, getDeployments: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { list := &kapi.ReplicationControllerList{} for _, d := range s.deployments { list.Items = append(list.Items, *d) } return list, nil }, scaler: scaler, } err := deployer.Deploy(to.Namespace, to.Name) if err != nil { t.Fatalf("unexpected error: %v", err) } if s.fromVersion > 0 { if e, a := s.fromVersion, deployutil.DeploymentVersionFor(actualFrom); e != a { t.Fatalf("expected from.latestVersion %d, got %d", e, a) } } if e, a := s.toVersion, deployutil.DeploymentVersionFor(actualTo); e != a { t.Fatalf("expected to.latestVersion %d, got %d", e, a) } if e, a := len(s.scaleEvents), len(scaler.Events); e != a { t.Fatalf("expected %d scale events, got %d", e, a) } for _, expected := range s.scaleEvents { expectedTo := findDeployment(expected.version) expectedWasScaled := false for _, actual := range scaler.Events { if actual.Name != expectedTo.Name { continue } if e, a := uint(expected.size), actual.Size; e != a { t.Fatalf("expected version %d to be scaled to %d, got %d", expected.version, e, a) } expectedWasScaled = true } if !expectedWasScaled { t.Fatalf("expected version %d to be scaled to %d, but it wasn't scaled at all", expected.version, expected.size) } } } }
func TestTriggers_manual(t *testing.T) { const namespace = "test-triggers-manual" testutil.RequireEtcd(t) defer testutil.DumpEtcdOnFailure(t) _, clusterAdminKubeConfig, err := testserver.StartTestMaster() if err != nil { t.Fatal(err) } clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) if err != nil { t.Fatal(err) } clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) if err != nil { t.Fatal(err) } _, err = testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, namespace, "my-test-user") if err != nil { t.Fatal(err) } osClient, kubeClient, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, "my-test-user") if err != nil { t.Fatal(err) } config := deploytest.OkDeploymentConfig(0) config.Namespace = namespace config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{{Type: deployapi.DeploymentTriggerManual}} dc, err := osClient.DeploymentConfigs(namespace).Create(config) if err != nil { t.Fatalf("Couldn't create DeploymentConfig: %v %#v", err, config) } rcWatch, err := kubeClient.ReplicationControllers(namespace).Watch(kapi.ListOptions{ResourceVersion: dc.ResourceVersion}) if err != nil { t.Fatalf("Couldn't subscribe to Deployments: %v", err) } defer rcWatch.Stop() request := &deployapi.DeploymentRequest{ Name: config.Name, Latest: false, Force: true, } config, err = osClient.DeploymentConfigs(namespace).Instantiate(request) if err != nil { t.Fatalf("Couldn't instantiate deployment config %q: %v", config.Name, err) } if config.Status.LatestVersion != 1 { t.Fatal("Instantiated deployment config should have version 1") } if config.Status.Details == nil || len(config.Status.Details.Causes) == 0 { t.Fatal("Instantiated deployment config should have a cause of deployment") } gotType := config.Status.Details.Causes[0].Type if gotType != deployapi.DeploymentTriggerManual { t.Fatalf("Instantiated deployment config should have a %q cause of deployment instead of %q", deployapi.DeploymentTriggerManual, gotType) } event := <-rcWatch.ResultChan() if e, a := watchapi.Added, event.Type; e != a { t.Fatalf("expected watch event type %s, got %s", e, a) } deployment := event.Object.(*kapi.ReplicationController) if e, a := config.Name, deployutil.DeploymentConfigNameFor(deployment); e != a { t.Fatalf("Expected deployment annotated with deploymentConfig '%s', got '%s'", e, a) } if e, a := int64(1), deployutil.DeploymentVersionFor(deployment); e != a { t.Fatalf("Deployment annotation version does not match: %#v", deployment) } }
func (m RecentDeploymentReferences) Less(i, j int) bool { return deployutil.DeploymentVersionFor(m[i]) > deployutil.DeploymentVersionFor(m[j]) }
// Describe returns the description of a DeploymentConfig func (d *DeploymentConfigDescriber) Describe(namespace, name string, settings kctl.DescriberSettings) (string, error) { var deploymentConfig *deployapi.DeploymentConfig if d.config != nil { // If a deployment config is already provided use that. // This is used by `oc rollback --dry-run`. deploymentConfig = d.config } else { var err error deploymentConfig, err = d.osClient.DeploymentConfigs(namespace).Get(name) if err != nil { return "", err } } return tabbedString(func(out *tabwriter.Writer) error { formatMeta(out, deploymentConfig.ObjectMeta) var ( deploymentsHistory []kapi.ReplicationController activeDeploymentName string ) if d.config == nil { if rcs, err := d.kubeClient.ReplicationControllers(namespace).List(kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(deploymentConfig.Name)}); err == nil { deploymentsHistory = rcs.Items } } if deploymentConfig.Status.LatestVersion == 0 { formatString(out, "Latest Version", "Not deployed") } else { formatString(out, "Latest Version", strconv.FormatInt(deploymentConfig.Status.LatestVersion, 10)) } printDeploymentConfigSpec(d.kubeClient, *deploymentConfig, out) fmt.Fprintln(out) latestDeploymentName := deployutil.LatestDeploymentNameForConfig(deploymentConfig) if activeDeployment := deployutil.ActiveDeployment(deploymentConfig, deploymentsHistory); activeDeployment != nil { activeDeploymentName = activeDeployment.Name } var deployment *kapi.ReplicationController isNotDeployed := len(deploymentsHistory) == 0 for _, item := range deploymentsHistory { if item.Name == latestDeploymentName { deployment = &item } } if isNotDeployed { formatString(out, "Latest Deployment", "<none>") } else { header := fmt.Sprintf("Deployment #%d (latest)", deployutil.DeploymentVersionFor(deployment)) // Show details if the current deployment is the active one or it is the // initial deployment. printDeploymentRc(deployment, d.kubeClient, out, header, (deployment.Name == activeDeploymentName) || len(deploymentsHistory) == 1) } // We don't show the deployment history when running `oc rollback --dry-run`. if d.config == nil && !isNotDeployed { sorted := deploymentsHistory sort.Sort(sort.Reverse(rcutils.OverlappingControllers(sorted))) counter := 1 for _, item := range sorted { if item.Name != latestDeploymentName && deploymentConfig.Name == deployutil.DeploymentConfigNameFor(&item) { header := fmt.Sprintf("Deployment #%d", deployutil.DeploymentVersionFor(&item)) printDeploymentRc(&item, d.kubeClient, out, header, item.Name == activeDeploymentName) counter++ } if counter == maxDisplayDeployments { break } } } if settings.ShowEvents { // Events if events, err := d.kubeClient.Events(deploymentConfig.Namespace).Search(deploymentConfig); err == nil && events != nil { latestDeploymentEvents := &kapi.EventList{Items: []kapi.Event{}} for i := len(events.Items); i != 0 && i > len(events.Items)-maxDisplayDeploymentsEvents; i-- { latestDeploymentEvents.Items = append(latestDeploymentEvents.Items, events.Items[i-1]) } fmt.Fprintln(out) kctl.DescribeEvents(latestDeploymentEvents, out) } } return nil }) }
// TestHandle_existingDeployments ensures that an attempt to create a // new deployment for a config that has existing deployments succeeds of fails // depending upon the state of the existing deployments func TestHandle_existingDeployments(t *testing.T) { var ( config *deployapi.DeploymentConfig deployed *kapi.ReplicationController existingDeployments *kapi.ReplicationControllerList updatedDeployments []kapi.ReplicationController ) controller := &DeploymentConfigController{ makeDeployment: func(cfg *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) { return deployutil.MakeDeployment(cfg, api.Codec) }, deploymentClient: &deploymentClientImpl{ createDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { deployed = deployment return deployment, nil }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { return existingDeployments, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployments = append(updatedDeployments, *deployment) return deployment, nil }, }, osClient: testclient.NewSimpleFake(), } type existing struct { version int status deployapi.DeploymentStatus shouldCancel bool } type scenario struct { version int existing []existing errorType reflect.Type expectDeployment bool } transientErrorType := reflect.TypeOf(transientError("")) scenarios := []scenario{ // No existing deployments {1, []existing{}, nil, true}, // A single existing completed deployment {2, []existing{{1, deployapi.DeploymentStatusComplete, false}}, nil, true}, // A single existing failed deployment {2, []existing{{1, deployapi.DeploymentStatusFailed, false}}, nil, true}, // Multiple existing completed/failed deployments {3, []existing{{2, deployapi.DeploymentStatusFailed, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, true}, // A single existing deployment in the default state {2, []existing{{1, "", false}}, transientErrorType, false}, // A single existing new deployment {2, []existing{{1, deployapi.DeploymentStatusNew, false}}, transientErrorType, false}, // A single existing pending deployment {2, []existing{{1, deployapi.DeploymentStatusPending, false}}, transientErrorType, false}, // A single existing running deployment {2, []existing{{1, deployapi.DeploymentStatusRunning, false}}, transientErrorType, false}, // Multiple existing deployments with one in new/pending/running {4, []existing{{3, deployapi.DeploymentStatusRunning, false}, {2, deployapi.DeploymentStatusComplete, false}, {1, deployapi.DeploymentStatusFailed, false}}, transientErrorType, false}, // Latest deployment exists and has already failed/completed {2, []existing{{2, deployapi.DeploymentStatusFailed, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, false}, // Latest deployment exists and is in new/pending/running state {2, []existing{{2, deployapi.DeploymentStatusRunning, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, false}, // Multiple existing deployments with more than one in new/pending/running {4, []existing{{3, deployapi.DeploymentStatusNew, false}, {2, deployapi.DeploymentStatusRunning, true}, {1, deployapi.DeploymentStatusFailed, false}}, transientErrorType, false}, // Multiple existing deployments with more than one in new/pending/running // Latest deployment has already failed {6, []existing{{5, deployapi.DeploymentStatusFailed, false}, {4, deployapi.DeploymentStatusRunning, false}, {3, deployapi.DeploymentStatusNew, true}, {2, deployapi.DeploymentStatusComplete, false}, {1, deployapi.DeploymentStatusNew, true}}, transientErrorType, false}, } for _, scenario := range scenarios { updatedDeployments = []kapi.ReplicationController{} deployed = nil config = deploytest.OkDeploymentConfig(scenario.version) existingDeployments = &kapi.ReplicationControllerList{} for _, e := range scenario.existing { d, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(e.version), api.Codec) if e.status != "" { d.Annotations[deployapi.DeploymentStatusAnnotation] = string(e.status) } existingDeployments.Items = append(existingDeployments.Items, *d) } controller.osClient = testclient.NewSimpleFake(config) err := controller.Handle(config) if scenario.expectDeployment && deployed == nil { t.Fatalf("expected a deployment") } if scenario.errorType == nil { if err != nil { t.Fatalf("unexpected error: %v", err) } } else { if err == nil { t.Fatalf("expected error") } if reflect.TypeOf(err) != scenario.errorType { t.Fatalf("error expected: %s, got: %s", scenario.errorType, reflect.TypeOf(err)) } } expectedCancellations := []int{} actualCancellations := []int{} for _, e := range scenario.existing { if e.shouldCancel { expectedCancellations = append(expectedCancellations, e.version) } } for _, d := range updatedDeployments { actualCancellations = append(actualCancellations, deployutil.DeploymentVersionFor(&d)) } sort.Ints(actualCancellations) sort.Ints(expectedCancellations) if !reflect.DeepEqual(actualCancellations, expectedCancellations) { t.Fatalf("expected cancellations: %v, actual: %v", expectedCancellations, actualCancellations) } } }
// Deploy starts the deployment process for rcName. func (d *Deployer) Deploy(namespace, rcName string) error { // Look up the new deployment. to, err := d.getDeployment(namespace, rcName) if err != nil { return fmt.Errorf("couldn't get deployment %s: %v", rcName, err) } // Decode the config from the deployment. config, err := deployutil.DecodeDeploymentConfig(to, kapi.Codecs.UniversalDecoder()) if err != nil { return fmt.Errorf("couldn't decode deployment config from deployment %s: %v", to.Name, err) } // Get a strategy for the deployment. s, err := d.strategyFor(config) if err != nil { return err } // New deployments must have a desired replica count. desiredReplicas, hasDesired := deployutil.DeploymentDesiredReplicas(to) if !hasDesired { return fmt.Errorf("deployment %s has already run to completion", to.Name) } // Find all deployments for the config. unsortedDeployments, err := d.getDeployments(namespace, config.Name) if err != nil { return fmt.Errorf("couldn't get controllers in namespace %s: %v", namespace, err) } deployments := unsortedDeployments.Items // Sort all the deployments by version. sort.Sort(deployutil.ByLatestVersionDesc(deployments)) // Find any last completed deployment. var from *kapi.ReplicationController for _, candidate := range deployments { if candidate.Name == to.Name { continue } if deployutil.IsCompleteDeployment(&candidate) { from = &candidate break } } if deployutil.DeploymentVersionFor(to) < deployutil.DeploymentVersionFor(from) { return fmt.Errorf("deployment %s is older than %s", to.Name, from.Name) } // Scale down any deployments which aren't the new or last deployment. for _, candidate := range deployments { // Skip the from/to deployments. if candidate.Name == to.Name { continue } if from != nil && candidate.Name == from.Name { continue } // Skip the deployment if it's already scaled down. if candidate.Spec.Replicas == 0 { continue } // Scale the deployment down to zero. retryWaitParams := kubectl.NewRetryParams(1*time.Second, 120*time.Second) if err := d.scaler.Scale(candidate.Namespace, candidate.Name, uint(0), &kubectl.ScalePrecondition{Size: -1, ResourceVersion: ""}, retryWaitParams, retryWaitParams); err != nil { fmt.Fprintf(d.errOut, "error: Couldn't scale down prior deployment %s: %v\n", deployutil.LabelForDeployment(&candidate), err) } else { fmt.Fprintf(d.out, "--> Scaled older deployment %s down\n", candidate.Name) } } if d.until == "start" { return strategy.NewConditionReachedErr("Ready to start deployment") } // Perform the deployment. if err := s.Deploy(from, to, int(desiredReplicas)); err != nil { return err } fmt.Fprintf(d.out, "--> Success\n") return nil }
func TestTriggers_manual(t *testing.T) { const namespace = "test-triggers-manual" _, clusterAdminKubeConfig, err := testserver.StartTestMaster() checkErr(t, err) clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) checkErr(t, err) clusterAdminClient, err := testutil.GetClusterAdminClient(clusterAdminKubeConfig) checkErr(t, err) _, err = testserver.CreateNewProject(clusterAdminClient, *clusterAdminClientConfig, namespace, "my-test-user") checkErr(t, err) osClient, kubeClient, _, err := testutil.GetClientForUser(*clusterAdminClientConfig, "my-test-user") checkErr(t, err) config := deploytest.OkDeploymentConfig(0) config.Namespace = namespace config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{ { Type: deployapi.DeploymentTriggerManual, }, } dc, err := osClient.DeploymentConfigs(namespace).Create(config) if err != nil { t.Fatalf("Couldn't create DeploymentConfig: %v %#v", err, config) } rcWatch, err := kubeClient.ReplicationControllers(namespace).Watch(labels.Everything(), fields.Everything(), dc.ResourceVersion) if err != nil { t.Fatalf("Couldn't subscribe to Deployments: %v", err) } defer rcWatch.Stop() retryErr := kclient.RetryOnConflict(wait.Backoff{Steps: maxUpdateRetries}, func() error { config, err := osClient.DeploymentConfigs(namespace).Generate(config.Name) if err != nil { return err } if config.Status.LatestVersion != 1 { t.Fatalf("Generated deployment should have version 1: %#v", config) } t.Logf("config(1): %#v", config) updatedConfig, err := osClient.DeploymentConfigs(namespace).Update(config) if err != nil { return err } t.Logf("config(2): %#v", updatedConfig) return nil }) if retryErr != nil { t.Fatal(err) } event := <-rcWatch.ResultChan() if e, a := watchapi.Added, event.Type; e != a { t.Fatalf("expected watch event type %s, got %s", e, a) } deployment := event.Object.(*kapi.ReplicationController) if e, a := config.Name, deployutil.DeploymentConfigNameFor(deployment); e != a { t.Fatalf("Expected deployment annotated with deploymentConfig '%s', got '%s'", e, a) } if e, a := 1, deployutil.DeploymentVersionFor(deployment); e != a { t.Fatalf("Deployment annotation version does not match: %#v", deployment) } }
// TestCmdDeploy_cancelOk ensures that attempts to cancel deployments // for a config result in cancelling all in-progress deployments // and none of the completed/faild ones. func TestCmdDeploy_cancelOk(t *testing.T) { type existing struct { version int status deployapi.DeploymentStatus shouldCancel bool } type scenario struct { version int existing []existing } scenarios := []scenario{ // No existing deployments {1, []existing{{1, deployapi.DeploymentStatusComplete, false}}}, // A single existing failed deployment {1, []existing{{1, deployapi.DeploymentStatusFailed, false}}}, // Multiple existing completed/failed deployments {2, []existing{{2, deployapi.DeploymentStatusFailed, false}, {1, deployapi.DeploymentStatusComplete, false}}}, // A single existing new deployment {1, []existing{{1, deployapi.DeploymentStatusNew, true}}}, // A single existing pending deployment {1, []existing{{1, deployapi.DeploymentStatusPending, true}}}, // A single existing running deployment {1, []existing{{1, deployapi.DeploymentStatusRunning, true}}}, // Multiple existing deployments with one in new/pending/running {3, []existing{{3, deployapi.DeploymentStatusRunning, true}, {2, deployapi.DeploymentStatusComplete, false}, {1, deployapi.DeploymentStatusFailed, false}}}, // Multiple existing deployments with more than one in new/pending/running {3, []existing{{3, deployapi.DeploymentStatusNew, true}, {2, deployapi.DeploymentStatusRunning, true}, {1, deployapi.DeploymentStatusFailed, false}}}, } for _, scenario := range scenarios { updatedDeployments := []kapi.ReplicationController{} config := deploytest.OkDeploymentConfig(scenario.version) existingDeployments := &kapi.ReplicationControllerList{} for _, e := range scenario.existing { d, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(e.version), api.Codec) d.Annotations[deployapi.DeploymentStatusAnnotation] = string(e.status) existingDeployments.Items = append(existingDeployments.Items, *d) } kubeClient := &ktc.Fake{} kubeClient.AddReactor("update", "replicationcontrollers", func(action ktc.Action) (handled bool, ret runtime.Object, err error) { updated := action.(ktc.UpdateAction).GetObject().(*kapi.ReplicationController) updatedDeployments = append(updatedDeployments, *updated) return true, updated, nil }) kubeClient.AddReactor("list", "replicationcontrollers", func(action ktc.Action) (handled bool, ret runtime.Object, err error) { return true, existingDeployments, nil }) o := &DeployOptions{kubeClient: kubeClient} err := o.cancel(config, ioutil.Discard) if err != nil { t.Fatalf("unexpected error: %v", err) } expectedCancellations := []int{} actualCancellations := []int{} for _, e := range scenario.existing { if e.shouldCancel { expectedCancellations = append(expectedCancellations, e.version) } } for _, d := range updatedDeployments { actualCancellations = append(actualCancellations, deployutil.DeploymentVersionFor(&d)) } sort.Ints(actualCancellations) sort.Ints(expectedCancellations) if !reflect.DeepEqual(actualCancellations, expectedCancellations) { t.Fatalf("expected cancellations: %v, actual: %v", expectedCancellations, actualCancellations) } } }
func (m RecentDeploymentReferences) Less(i, j int) bool { return deployutil.DeploymentVersionFor(m[i].ReplicationController) > deployutil.DeploymentVersionFor(m[j].ReplicationController) }
// Describe returns the description of a DeploymentConfig func (d *DeploymentConfigDescriber) Describe(namespace, name string, settings kctl.DescriberSettings) (string, error) { var deploymentConfig *deployapi.DeploymentConfig if d.config != nil { // If a deployment config is already provided use that. // This is used by `oc rollback --dry-run`. deploymentConfig = d.config } else { var err error deploymentConfig, err = d.osClient.DeploymentConfigs(namespace).Get(name) if err != nil { return "", err } } return tabbedString(func(out *tabwriter.Writer) error { formatMeta(out, deploymentConfig.ObjectMeta) if deploymentConfig.Status.LatestVersion == 0 { formatString(out, "Latest Version", "Not deployed") } else { formatString(out, "Latest Version", strconv.FormatInt(deploymentConfig.Status.LatestVersion, 10)) } printDeploymentConfigSpec(d.kubeClient, *deploymentConfig, out) fmt.Fprintln(out) if deploymentConfig.Status.Details != nil && len(deploymentConfig.Status.Details.Message) > 0 { fmt.Fprintf(out, "Warning:\t%s\n", deploymentConfig.Status.Details.Message) } deploymentName := deployutil.LatestDeploymentNameForConfig(deploymentConfig) deployment, err := d.kubeClient.ReplicationControllers(namespace).Get(deploymentName) if err != nil { if kerrors.IsNotFound(err) { formatString(out, "Latest Deployment", "<none>") } else { formatString(out, "Latest Deployment", fmt.Sprintf("error: %v", err)) } } else { header := fmt.Sprintf("Deployment #%d (latest)", deployutil.DeploymentVersionFor(deployment)) printDeploymentRc(deployment, d.kubeClient, out, header, true) } // We don't show the deployment history when running `oc rollback --dry-run`. if d.config == nil { deploymentsHistory, err := d.kubeClient.ReplicationControllers(namespace).List(kapi.ListOptions{LabelSelector: labels.Everything()}) if err == nil { sorted := deploymentsHistory.Items sort.Sort(sort.Reverse(rcutils.OverlappingControllers(sorted))) counter := 1 for _, item := range sorted { if item.Name != deploymentName && deploymentConfig.Name == deployutil.DeploymentConfigNameFor(&item) { header := fmt.Sprintf("Deployment #%d", deployutil.DeploymentVersionFor(&item)) printDeploymentRc(&item, d.kubeClient, out, header, false) counter++ } if counter == maxDisplayDeployments { break } } } } if settings.ShowEvents { // Events if events, err := d.kubeClient.Events(deploymentConfig.Namespace).Search(deploymentConfig); err == nil && events != nil { latestDeploymentEvents := &kapi.EventList{Items: []kapi.Event{}} for i := len(events.Items); i != 0 && i > len(events.Items)-maxDisplayDeploymentsEvents; i-- { latestDeploymentEvents.Items = append(latestDeploymentEvents.Items, events.Items[i-1]) } fmt.Fprintln(out) kctl.DescribeEvents(latestDeploymentEvents, out) } } return nil }) }
// Handle processes config and creates a new deployment if necessary. func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig) error { // Inspect a deployment configuration every time the controller reconciles it details, existingDeployments, latestDeploymentExists, err := c.findDetails(config) if err != nil { return err } config, err = c.updateDetails(config, details) if err != nil { return transientError(err.Error()) } // Only deploy when the version has advanced past 0. if config.LatestVersion == 0 { glog.V(5).Infof("Waiting for first version of %s", deployutil.LabelForDeploymentConfig(config)) return nil } var inflightDeployment *kapi.ReplicationController for _, deployment := range existingDeployments.Items { deploymentStatus := deployutil.DeploymentStatusFor(&deployment) switch deploymentStatus { case deployapi.DeploymentStatusFailed, deployapi.DeploymentStatusComplete: // Previous deployment in terminal state - can ignore // Ignoring specific deployment states so that any newly introduced // deployment state will not be ignored default: if inflightDeployment == nil { inflightDeployment = &deployment continue } var deploymentForCancellation *kapi.ReplicationController if deployutil.DeploymentVersionFor(inflightDeployment) < deployutil.DeploymentVersionFor(&deployment) { deploymentForCancellation, inflightDeployment = inflightDeployment, &deployment } else { deploymentForCancellation = &deployment } deploymentForCancellation.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue deploymentForCancellation.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledNewerDeploymentExists if _, err := c.deploymentClient.updateDeployment(deploymentForCancellation.Namespace, deploymentForCancellation); err != nil { util.HandleError(fmt.Errorf("couldn't cancel deployment %s: %v", deployutil.LabelForDeployment(deploymentForCancellation), err)) } glog.V(4).Infof("Cancelled deployment %s for deployment config %s", deployutil.LabelForDeployment(deploymentForCancellation), deployutil.LabelForDeploymentConfig(config)) } } // if the latest deployment exists then nothing else needs to be done if latestDeploymentExists { return nil } // check to see if there are inflight deployments if inflightDeployment != nil { // raise a transientError so that the deployment config can be re-queued glog.V(4).Infof("Found previous inflight deployment for %s - will requeue", deployutil.LabelForDeploymentConfig(config)) return transientError(fmt.Sprintf("found previous inflight deployment for %s - requeuing", deployutil.LabelForDeploymentConfig(config))) } // Try and build a deployment for the config. deployment, err := c.makeDeployment(config) if err != nil { return fatalError(fmt.Sprintf("couldn't make deployment from (potentially invalid) deployment config %s: %v", deployutil.LabelForDeploymentConfig(config), err)) } // Compute the desired replicas for the deployment. Use the last completed // deployment's current replica count, or the config template if there is no // prior completed deployment available. desiredReplicas := config.Template.ControllerTemplate.Replicas if len(existingDeployments.Items) > 0 { sort.Sort(deployutil.ByLatestVersionDesc(existingDeployments.Items)) for _, existing := range existingDeployments.Items { if deployutil.DeploymentStatusFor(&existing) == deployapi.DeploymentStatusComplete { desiredReplicas = existing.Spec.Replicas glog.V(4).Infof("Desired replicas for %s set to %d based on prior completed deployment %s", deployutil.LabelForDeploymentConfig(config), desiredReplicas, existing.Name) break } } } deployment.Annotations[deployapi.DesiredReplicasAnnotation] = strconv.Itoa(desiredReplicas) // Create the deployment. if _, err := c.deploymentClient.createDeployment(config.Namespace, deployment); err == nil { glog.V(4).Infof("Created deployment for deployment config %s", deployutil.LabelForDeploymentConfig(config)) return nil } else { // If the deployment was already created, just move on. The cache could be stale, or another // process could have already handled this update. if errors.IsAlreadyExists(err) { glog.V(4).Infof("Deployment already exists for deployment config %s", deployutil.LabelForDeploymentConfig(config)) return nil } glog.Warningf("Cannot create latest deployment for deployment config %q: %v", deployutil.LabelForDeploymentConfig(config), err) return fmt.Errorf("couldn't create deployment for deployment config %s: %v", deployutil.LabelForDeploymentConfig(config), err) } }