diff --git a/pkg/cmd/cli/describe/projectstatus.go b/pkg/cmd/cli/describe/projectstatus.go index e2a58f762701..6e627439922e 100644 --- a/pkg/cmd/cli/describe/projectstatus.go +++ b/pkg/cmd/cli/describe/projectstatus.go @@ -109,8 +109,8 @@ func (d *ProjectStatusDescriber) Describe(namespace, name string) (string, error if len(groups) == 0 { fmt.Fprintln(out, "\nYou have no services, deployment configs, or build configs. 'osc new-app' can be used to create applications from scratch from existing Docker images and templates.") } else { - fmt.Fprintln(out, "\nTo see more information about a service or deployment config, use 'osc describe service ' or 'osc describe dc '.") - fmt.Fprintln(out, "You can use 'osc get pods,svc,dc,bc,builds' to see lists of each of the types described above.") + fmt.Fprintln(out, "\nTo see more information about a service or deployment, use 'osc describe service ' or 'osc describe dc '.") + fmt.Fprintln(out, "You can use 'osc get all' to see lists of each of the types described above.") } return nil @@ -371,7 +371,7 @@ func describeDeployments(node *graph.DeploymentConfigNode, count int) []string { } for i, deployment := range deployments { - out = append(out, describeDeploymentStatus(deployment)) + out = append(out, describeDeploymentStatus(deployment, i == 0)) switch { case count == -1: @@ -387,20 +387,61 @@ func describeDeployments(node *graph.DeploymentConfigNode, count int) []string { return out } -func describeDeploymentStatus(deploy *kapi.ReplicationController) string { +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", version, timeAt) + return fmt.Sprintf("#%d deployment failed %s ago%s%s", version, timeAt, reason, describeDeploymentPodSummaryInline(deploy, false)) case deployapi.DeploymentStatusComplete: // TODO: pod status output - return fmt.Sprintf("#%d deployed %s ago", version, timeAt) + return fmt.Sprintf("#%d deployed %s ago%s", version, timeAt, describeDeploymentPodSummaryInline(deploy, first)) + case deployapi.DeploymentStatusRunning: + return fmt.Sprintf("#%d deployment running for %s%s", version, timeAt, describeDeploymentPodSummaryInline(deploy, false)) default: - return fmt.Sprintf("#%d deployment %s %s ago", version, strings.ToLower(string(status)), timeAt) + return fmt.Sprintf("#%d deployment %s %s ago%s", version, strings.ToLower(string(status)), timeAt, describeDeploymentPodSummaryInline(deploy, false)) + } +} + +func describeDeploymentPodSummaryInline(deploy *kapi.ReplicationController, includeEmpty bool) string { + s := describeDeploymentPodSummary(deploy, includeEmpty) + if len(s) == 0 { + return s + } + change := "" + if changing, ok := deployutil.DeploymentDesiredReplicas(deploy); ok { + switch { + case changing < deploy.Spec.Replicas: + change = fmt.Sprintf(" reducing to %d", changing) + case changing > deploy.Spec.Replicas: + change = fmt.Sprintf(" growing to %d", changing) + } + } + return fmt.Sprintf(" - %s%s", s, change) +} + +func describeDeploymentPodSummary(deploy *kapi.ReplicationController, includeEmpty bool) string { + actual, requested := deploy.Status.Replicas, deploy.Spec.Replicas + if actual == requested { + switch { + case actual == 0: + if !includeEmpty { + return "" + } + return "0 pods" + case actual > 1: + return fmt.Sprintf("%d pods", actual) + default: + return "1 pod" + } } + return fmt.Sprintf("%d/%d pods", actual, requested) } func describeDeploymentConfigTriggers(config *deployapi.DeploymentConfig) (string, bool) { diff --git a/pkg/cmd/cli/describe/projectstatus_test.go b/pkg/cmd/cli/describe/projectstatus_test.go index fc2b3c6f3582..70a8afd017bc 100644 --- a/pkg/cmd/cli/describe/projectstatus_test.go +++ b/pkg/cmd/cli/describe/projectstatus_test.go @@ -137,6 +137,8 @@ func TestProjectStatus(t *testing.T) { "database deploys", "frontend deploys", "with docker.io/openshift/ruby-20-centos7:latest", + "#2 deployment failed less than a second ago: unable to contact server - 0/1 pods", + "#2 deployment running for 7 seconds - 2/1 pods", "#1 deployed 8 seconds ago", "#1 deployed less than a second ago", "To see more information", @@ -176,6 +178,6 @@ func TestProjectStatus(t *testing.T) { t.Errorf("%s: did not have %q:\n%s\n---", k, s, out) } } - //t.Logf("\n%s", out) + t.Logf("\n%s", out) } } diff --git a/pkg/cmd/server/admin/create_nodeconfig.go b/pkg/cmd/server/admin/create_nodeconfig.go index df6c69adf5ef..1d3ea78c186f 100644 --- a/pkg/cmd/server/admin/create_nodeconfig.go +++ b/pkg/cmd/server/admin/create_nodeconfig.go @@ -103,7 +103,8 @@ func NewCommandNodeConfig(commandName string, fullName string, out io.Writer) *c func NewDefaultCreateNodeConfigOptions() *CreateNodeConfigOptions { options := &CreateNodeConfigOptions{GetSignerCertOptions: &GetSignerCertOptions{}} options.VolumeDir = "openshift.local.volumes" - options.DNSDomain = "local" + // TODO: replace me with a proper round trip of config options through decode + options.DNSDomain = "cluster.local" options.APIServerURL = "https://localhost:8443" options.APIServerCAFile = "openshift.local.config/master/ca.crt" options.NodeClientCAFile = "openshift.local.config/master/ca.crt" diff --git a/pkg/deploy/controller/deployerpod/factory.go b/pkg/deploy/controller/deployerpod/factory.go index 7b9f70f283be..10006c1e67d5 100644 --- a/pkg/deploy/controller/deployerpod/factory.go +++ b/pkg/deploy/controller/deployerpod/factory.go @@ -110,7 +110,7 @@ func pollPods(deploymentStore cache.Store, kClient kclient.Interface) (cache.Enu if kerrors.IsNotFound(err) { nextStatus := deployapi.DeploymentStatusFailed deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) - deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = fmt.Sprintf("Couldn't find pod %s for deployment %s", podID, deployment.Name) + deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = fmt.Sprintf("deployment process pod %q was deleted before completion", podID) if _, err := kClient.ReplicationControllers(deployment.Namespace).Update(deployment); err != nil { glog.Errorf("couldn't update deployment %s to status %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) diff --git a/pkg/deploy/util/util.go b/pkg/deploy/util/util.go index b08729c6b336..2a6d40bedd8e 100644 --- a/pkg/deploy/util/util.go +++ b/pkg/deploy/util/util.go @@ -18,7 +18,8 @@ import ( deployv3 "github.com/openshift/origin/pkg/deploy/api/v1beta3" ) -// Maps the latest annotation keys to all known previous key names. +// Maps the latest annotation keys to all known previous key names. Keys not represented here +// may still be looked up directly via mappedAnnotationFor var annotationMap = map[string][]string{ deployapi.DeploymentConfigAnnotation: { deployv1.DeploymentConfigAnnotation, @@ -219,6 +220,22 @@ func DeploymentStatusFor(obj runtime.Object) deployapi.DeploymentStatus { return deployapi.DeploymentStatus(mappedAnnotationFor(obj, deployapi.DeploymentStatusAnnotation)) } +func DeploymentStatusReasonFor(obj runtime.Object) string { + return mappedAnnotationFor(obj, deployapi.DeploymentStatusReasonAnnotation) +} + +func DeploymentDesiredReplicas(obj runtime.Object) (int, bool) { + s := mappedAnnotationFor(obj, deployapi.DesiredReplicasAnnotation) + if len(s) == 0 { + return 0, false + } + i, err := strconv.Atoi(s) + if err != nil { + return 0, false + } + return i, true +} + func EncodedDeploymentConfigFor(obj runtime.Object) string { return mappedAnnotationFor(obj, deployapi.DeploymentEncodedConfigAnnotation) } @@ -239,10 +256,12 @@ func mappedAnnotationFor(obj runtime.Object, key string) string { return "" } for _, mappedKey := range annotationMap[key] { - val, hasVal := meta.Annotations[mappedKey] - if hasVal { + if val, ok := meta.Annotations[mappedKey]; ok { return val } } + if val, ok := meta.Annotations[key]; ok { + return val + } return "" } diff --git a/test/fixtures/app-scenarios/new-project-deployed-app.yaml b/test/fixtures/app-scenarios/new-project-deployed-app.yaml index 05079b2cd745..af1b93695104 100644 --- a/test/fixtures/app-scenarios/new-project-deployed-app.yaml +++ b/test/fixtures/app-scenarios/new-project-deployed-app.yaml @@ -75,6 +75,76 @@ items: type: STI startTimestamp: 2015-04-07T04:12:21Z status: Complete +- annotations: + deploymentConfig: database + deploymentStatus: Running + deploymentVersion: "2" + encodedDeploymentConfig: '{"kind":"DeploymentConfig","apiVersion":"v1beta1","metadata":{"name":"database","namespace":"test","selfLink":"/osapi/v1beta1/watch/deploymentConfigs/database","uid":"4725b5d3-dcdc-11e4-968a-080027c5bfa9","resourceVersion":"271","creationTimestamp":"2015-04-07T04:12:17Z","labels":{"template":"application-template-stibuild"}},"triggers":[{"type":"ConfigChange"}],"template":{"strategy":{"type":"Recreate"},"controllerTemplate":{"replicas":1,"replicaSelector":{"name":"database"},"podTemplate":{"desiredState":{"manifest":{"version":"v1beta2","id":"","volumes":null,"containers":[{"name":"ruby-helloworld-database","image":"openshift/mysql-55-centos7","ports":[{"containerPort":3306,"protocol":"TCP"}],"env":[{"name":"MYSQL_USER","key":"MYSQL_USER","value":"user1CY"},{"name":"MYSQL_PASSWORD","key":"MYSQL_PASSWORD","value":"FfyXmsGG"},{"name":"MYSQL_DATABASE","key":"MYSQL_DATABASE","value":"root"}],"resources":{},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"PullIfNotPresent","capabilities":{}}],"restartPolicy":{"always":{}},"dnsPolicy":"ClusterFirst"}},"labels":{"name":"database","template":"application-template-stibuild"}}}},"latestVersion":1,"details":{"causes":[{"type":"ConfigChange"}]}}' + pod: deploy-database-19m1he + apiVersion: v1beta1 + creationTimestamp: 2015-04-07T04:12:18Z + currentState: + podTemplate: + desiredState: + manifest: + containers: null + id: "" + restartPolicy: {} + version: "" + volumes: null + replicas: 2 + desiredState: + podTemplate: + annotations: + deployment: database-2 + deploymentConfig: database + deploymentVersion: "2" + desiredState: + manifest: + containers: + - capabilities: {} + env: + - key: MYSQL_USER + name: MYSQL_USER + value: user1CY + - key: MYSQL_PASSWORD + name: MYSQL_PASSWORD + value: FfyXmsGG + - key: MYSQL_DATABASE + name: MYSQL_DATABASE + value: root + image: openshift/mysql-55-centos7 + imagePullPolicy: PullIfNotPresent + name: ruby-helloworld-database + ports: + - containerPort: 3306 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + dnsPolicy: ClusterFirst + id: "" + restartPolicy: + always: {} + version: v1beta2 + volumes: null + labels: + deployment: database-2 + deploymentconfig: database + name: database + template: application-template-stibuild + replicaSelector: + deployment: database-2 + deploymentconfig: database + name: database + replicas: 1 + id: database-2 + kind: ReplicationController + labels: + template: application-template-stibuild + namespace: example + resourceVersion: 318 + selfLink: /api/v1beta1/replicationControllers/database-1?namespace=example + uid: 473d4a73-dcdc-11e4-968a-080027c5bfa9 - annotations: deploymentConfig: database deploymentStatus: Complete @@ -201,6 +271,73 @@ items: type: Recreate triggers: - type: ConfigChange +- annotations: + deploymentConfig: frontend + deploymentStatus: Failed + openshift.io/deployment.status-reason: unable to contact server + deploymentVersion: "2" + encodedDeploymentConfig: '{"kind":"DeploymentConfig","apiVersion":"v1beta1","metadata":{"name":"frontend","namespace":"test","selfLink":"/osapi/v1beta1/watch/deploymentConfigs/frontend","uid":"471f24e3-dcdc-11e4-968a-080027c5bfa9","resourceVersion":"346","creationTimestamp":"2015-04-07T04:12:17Z","labels":{"template":"application-template-stibuild"}},"triggers":[{"type":"ImageChange","imageChangeParams":{"automatic":true,"containerNames":["ruby-helloworld"],"from":{"kind":"ImageRepository","name":"origin-ruby-sample"},"tag":"latest","lastTriggeredImage":"172.30.17.139:5000/test/origin-ruby-sample:73214fafa244cb8abbe55273dac5d237a589a5fc7ac09926a1756a42c21e8a58"}}],"template":{"strategy":{"type":"Recreate"},"controllerTemplate":{"replicas":1,"replicaSelector":{"name":"frontend"},"podTemplate":{"desiredState":{"manifest":{"version":"v1beta2","id":"","volumes":null,"containers":[{"name":"ruby-helloworld","image":"172.30.17.139:5000/test/origin-ruby-sample:73214fafa244cb8abbe55273dac5d237a589a5fc7ac09926a1756a42c21e8a58","ports":[{"containerPort":8080,"protocol":"TCP"}],"env":[{"name":"ADMIN_USERNAME","key":"ADMIN_USERNAME","value":"adminNPX"},{"name":"ADMIN_PASSWORD","key":"ADMIN_PASSWORD","value":"7q1IdEao"},{"name":"MYSQL_USER","key":"MYSQL_USER","value":"user1CY"},{"name":"MYSQL_PASSWORD","key":"MYSQL_PASSWORD","value":"FfyXmsGG"},{"name":"MYSQL_DATABASE","key":"MYSQL_DATABASE","value":"root"}],"resources":{},"terminationMessagePath":"/dev/termination-log","imagePullPolicy":"PullIfNotPresent","capabilities":{}}],"restartPolicy":{"always":{}},"dnsPolicy":"ClusterFirst"}},"labels":{"name":"frontend","template":"application-template-stibuild"}}}},"latestVersion":1,"details":{"causes":[{"type":"ImageChange","imageTrigger":{"repositoryName":"172.30.17.139:5000/test/origin-ruby-sample:73214fafa244cb8abbe55273dac5d237a589a5fc7ac09926a1756a42c21e8a58","tag":"latest"}}]}}' + pod: deploy-frontend-17mza9 + apiVersion: v1beta1 + creationTimestamp: 2015-04-07T04:12:53Z + desiredState: + podTemplate: + annotations: + deployment: frontend-2 + deploymentConfig: frontend + deploymentVersion: "2" + desiredState: + manifest: + containers: + - capabilities: {} + env: + - key: ADMIN_USERNAME + name: ADMIN_USERNAME + value: adminNPX + - key: ADMIN_PASSWORD + name: ADMIN_PASSWORD + value: 7q1IdEao + - key: MYSQL_USER + name: MYSQL_USER + value: user1CY + - key: MYSQL_PASSWORD + name: MYSQL_PASSWORD + value: FfyXmsGG + - key: MYSQL_DATABASE + name: MYSQL_DATABASE + value: root + image: 172.30.17.139:5000/test/origin-ruby-sample:73214fafa244cb8abbe55273dac5d237a589a5fc7ac09926a1756a42c21e8a58 + imagePullPolicy: PullIfNotPresent + name: ruby-helloworld + ports: + - containerPort: 8080 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + dnsPolicy: ClusterFirst + id: "" + restartPolicy: + always: {} + version: v1beta2 + volumes: null + labels: + deployment: frontend-2 + deploymentconfig: frontend + name: frontend + template: application-template-stibuild + replicaSelector: + deployment: frontend-2 + deploymentconfig: frontend + name: frontend + replicas: 1 + id: frontend-2 + kind: ReplicationController + labels: + template: application-template-stibuild + namespace: example + resourceVersion: 379 + selfLink: /api/v1beta1/replicationControllers/frontend-2?namespace=example + uid: 5c9cd4ec-dcdc-11e4-968a-080027c5bfa9 - annotations: deploymentConfig: frontend deploymentStatus: Complete @@ -285,7 +422,7 @@ items: tag: latest type: ImageChange kind: DeploymentConfig - latestVersion: 1 + latestVersion: 3 metadata: creationTimestamp: 2015-04-07T04:12:17Z labels: