1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package polymorphichelpers
18
19import (
20	"fmt"
21
22	appsv1 "k8s.io/api/apps/v1"
23	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
24	"k8s.io/apimachinery/pkg/runtime"
25	"k8s.io/apimachinery/pkg/runtime/schema"
26	deploymentutil "k8s.io/kubectl/pkg/util/deployment"
27)
28
29// StatusViewer provides an interface for resources that have rollout status.
30type StatusViewer interface {
31	Status(obj runtime.Unstructured, revision int64) (string, bool, error)
32}
33
34// StatusViewerFor returns a StatusViewer for the resource specified by kind.
35func StatusViewerFor(kind schema.GroupKind) (StatusViewer, error) {
36	switch kind {
37	case extensionsv1beta1.SchemeGroupVersion.WithKind("Deployment").GroupKind(),
38		appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind():
39		return &DeploymentStatusViewer{}, nil
40	case extensionsv1beta1.SchemeGroupVersion.WithKind("DaemonSet").GroupKind(),
41		appsv1.SchemeGroupVersion.WithKind("DaemonSet").GroupKind():
42		return &DaemonSetStatusViewer{}, nil
43	case appsv1.SchemeGroupVersion.WithKind("StatefulSet").GroupKind():
44		return &StatefulSetStatusViewer{}, nil
45	}
46	return nil, fmt.Errorf("no status viewer has been implemented for %v", kind)
47}
48
49// DeploymentStatusViewer implements the StatusViewer interface.
50type DeploymentStatusViewer struct{}
51
52// DaemonSetStatusViewer implements the StatusViewer interface.
53type DaemonSetStatusViewer struct{}
54
55// StatefulSetStatusViewer implements the StatusViewer interface.
56type StatefulSetStatusViewer struct{}
57
58// Status returns a message describing deployment status, and a bool value indicating if the status is considered done.
59func (s *DeploymentStatusViewer) Status(obj runtime.Unstructured, revision int64) (string, bool, error) {
60	deployment := &appsv1.Deployment{}
61	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), deployment)
62	if err != nil {
63		return "", false, fmt.Errorf("failed to convert %T to %T: %v", obj, deployment, err)
64	}
65
66	if revision > 0 {
67		deploymentRev, err := deploymentutil.Revision(deployment)
68		if err != nil {
69			return "", false, fmt.Errorf("cannot get the revision of deployment %q: %v", deployment.Name, err)
70		}
71		if revision != deploymentRev {
72			return "", false, fmt.Errorf("desired revision (%d) is different from the running revision (%d)", revision, deploymentRev)
73		}
74	}
75	if deployment.Generation <= deployment.Status.ObservedGeneration {
76		cond := deploymentutil.GetDeploymentCondition(deployment.Status, appsv1.DeploymentProgressing)
77		if cond != nil && cond.Reason == deploymentutil.TimedOutReason {
78			return "", false, fmt.Errorf("deployment %q exceeded its progress deadline", deployment.Name)
79		}
80		if deployment.Spec.Replicas != nil && deployment.Status.UpdatedReplicas < *deployment.Spec.Replicas {
81			return fmt.Sprintf("Waiting for deployment %q rollout to finish: %d out of %d new replicas have been updated...\n", deployment.Name, deployment.Status.UpdatedReplicas, *deployment.Spec.Replicas), false, nil
82		}
83		if deployment.Status.Replicas > deployment.Status.UpdatedReplicas {
84			return fmt.Sprintf("Waiting for deployment %q rollout to finish: %d old replicas are pending termination...\n", deployment.Name, deployment.Status.Replicas-deployment.Status.UpdatedReplicas), false, nil
85		}
86		if deployment.Status.AvailableReplicas < deployment.Status.UpdatedReplicas {
87			return fmt.Sprintf("Waiting for deployment %q rollout to finish: %d of %d updated replicas are available...\n", deployment.Name, deployment.Status.AvailableReplicas, deployment.Status.UpdatedReplicas), false, nil
88		}
89		return fmt.Sprintf("deployment %q successfully rolled out\n", deployment.Name), true, nil
90	}
91	return fmt.Sprintf("Waiting for deployment spec update to be observed...\n"), false, nil
92}
93
94// Status returns a message describing daemon set status, and a bool value indicating if the status is considered done.
95func (s *DaemonSetStatusViewer) Status(obj runtime.Unstructured, revision int64) (string, bool, error) {
96	//ignoring revision as DaemonSets does not have history yet
97
98	daemon := &appsv1.DaemonSet{}
99	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), daemon)
100	if err != nil {
101		return "", false, fmt.Errorf("failed to convert %T to %T: %v", obj, daemon, err)
102	}
103
104	if daemon.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType {
105		return "", true, fmt.Errorf("rollout status is only available for %s strategy type", appsv1.RollingUpdateStatefulSetStrategyType)
106	}
107	if daemon.Generation <= daemon.Status.ObservedGeneration {
108		if daemon.Status.UpdatedNumberScheduled < daemon.Status.DesiredNumberScheduled {
109			return fmt.Sprintf("Waiting for daemon set %q rollout to finish: %d out of %d new pods have been updated...\n", daemon.Name, daemon.Status.UpdatedNumberScheduled, daemon.Status.DesiredNumberScheduled), false, nil
110		}
111		if daemon.Status.NumberAvailable < daemon.Status.DesiredNumberScheduled {
112			return fmt.Sprintf("Waiting for daemon set %q rollout to finish: %d of %d updated pods are available...\n", daemon.Name, daemon.Status.NumberAvailable, daemon.Status.DesiredNumberScheduled), false, nil
113		}
114		return fmt.Sprintf("daemon set %q successfully rolled out\n", daemon.Name), true, nil
115	}
116	return fmt.Sprintf("Waiting for daemon set spec update to be observed...\n"), false, nil
117}
118
119// Status returns a message describing statefulset status, and a bool value indicating if the status is considered done.
120func (s *StatefulSetStatusViewer) Status(obj runtime.Unstructured, revision int64) (string, bool, error) {
121	sts := &appsv1.StatefulSet{}
122	err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), sts)
123	if err != nil {
124		return "", false, fmt.Errorf("failed to convert %T to %T: %v", obj, sts, err)
125	}
126
127	if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType {
128		return "", true, fmt.Errorf("rollout status is only available for %s strategy type", appsv1.RollingUpdateStatefulSetStrategyType)
129	}
130	if sts.Status.ObservedGeneration == 0 || sts.Generation > sts.Status.ObservedGeneration {
131		return "Waiting for statefulset spec update to be observed...\n", false, nil
132	}
133	if sts.Spec.Replicas != nil && sts.Status.ReadyReplicas < *sts.Spec.Replicas {
134		return fmt.Sprintf("Waiting for %d pods to be ready...\n", *sts.Spec.Replicas-sts.Status.ReadyReplicas), false, nil
135	}
136	if sts.Spec.UpdateStrategy.Type == appsv1.RollingUpdateStatefulSetStrategyType && sts.Spec.UpdateStrategy.RollingUpdate != nil {
137		if sts.Spec.Replicas != nil && sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil {
138			if sts.Status.UpdatedReplicas < (*sts.Spec.Replicas - *sts.Spec.UpdateStrategy.RollingUpdate.Partition) {
139				return fmt.Sprintf("Waiting for partitioned roll out to finish: %d out of %d new pods have been updated...\n",
140					sts.Status.UpdatedReplicas, *sts.Spec.Replicas-*sts.Spec.UpdateStrategy.RollingUpdate.Partition), false, nil
141			}
142		}
143		return fmt.Sprintf("partitioned roll out complete: %d new pods have been updated...\n",
144			sts.Status.UpdatedReplicas), true, nil
145	}
146	if sts.Status.UpdateRevision != sts.Status.CurrentRevision {
147		return fmt.Sprintf("waiting for statefulset rolling update to complete %d pods at revision %s...\n",
148			sts.Status.UpdatedReplicas, sts.Status.UpdateRevision), false, nil
149	}
150	return fmt.Sprintf("statefulset rolling update complete %d pods at revision %s...\n", sts.Status.CurrentReplicas, sts.Status.CurrentRevision), true, nil
151
152}
153