Merge pull request #19686 from janetkuo/rollback

Auto commit by PR queue bot
pull/6/head
k8s-merge-robot 2016-01-30 17:26:05 -08:00
commit 6a2a0e621b
22 changed files with 13473 additions and 11441 deletions

View File

@ -51,6 +51,7 @@ func addKnownTypes(scheme *runtime.Scheme) {
&ClusterAutoscalerList{},
&Deployment{},
&DeploymentList{},
&DeploymentRollback{},
&HorizontalPodAutoscaler{},
&HorizontalPodAutoscalerList{},
&Job{},
@ -76,6 +77,7 @@ func (obj *ClusterAutoscaler) GetObjectKind() unversioned.ObjectKind {
func (obj *ClusterAutoscalerList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Deployment) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *HorizontalPodAutoscaler) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *HorizontalPodAutoscalerList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Job) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }

File diff suppressed because it is too large Load Diff

View File

@ -236,6 +236,24 @@ type DeploymentSpec struct {
// Indicates that the deployment is paused and will not be processed by the
// deployment controller.
Paused bool `json:"paused,omitempty"`
// The config this deployment is rolling back to. Will be cleared after rollback is done.
RollbackTo *RollbackConfig `json:"rollbackTo,omitempty"`
}
// DeploymentRollback stores the information required to rollback a deployment.
type DeploymentRollback struct {
unversioned.TypeMeta `json:",inline"`
// Required: This must match the Name of a deployment.
Name string `json:"name"`
// The annotations to be updated to a deployment
UpdatedAnnotations map[string]string `json:"updatedAnnotations,omitempty"`
// The config of this deployment rollback.
RollbackTo RollbackConfig `json:"rollbackTo"`
}
type RollbackConfig struct {
// The revision to rollback to. If set to 0, rollbck to the last revision.
Revision int64 `json:"revision,omitempty"`
}
const (

View File

@ -267,6 +267,12 @@ func Convert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions.
out.UniqueLabelKey = new(string)
*out.UniqueLabelKey = in.UniqueLabelKey
out.Paused = in.Paused
if in.RollbackTo != nil {
out.RollbackTo = new(RollbackConfig)
out.RollbackTo.Revision = int64(in.RollbackTo.Revision)
} else {
out.RollbackTo = nil
}
return nil
}
@ -299,6 +305,12 @@ func Convert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *DeploymentS
out.UniqueLabelKey = *in.UniqueLabelKey
}
out.Paused = in.Paused
if in.RollbackTo != nil {
out.RollbackTo = new(extensions.RollbackConfig)
out.RollbackTo.Revision = in.RollbackTo.Revision
} else {
out.RollbackTo = nil
}
return nil
}

View File

@ -2678,6 +2678,32 @@ func Convert_extensions_DeploymentList_To_v1beta1_DeploymentList(in *extensions.
return autoConvert_extensions_DeploymentList_To_v1beta1_DeploymentList(in, out, s)
}
func autoConvert_extensions_DeploymentRollback_To_v1beta1_DeploymentRollback(in *extensions.DeploymentRollback, out *DeploymentRollback, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.DeploymentRollback))(in)
}
if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
out.Name = in.Name
if in.UpdatedAnnotations != nil {
out.UpdatedAnnotations = make(map[string]string)
for key, val := range in.UpdatedAnnotations {
out.UpdatedAnnotations[key] = val
}
} else {
out.UpdatedAnnotations = nil
}
if err := Convert_extensions_RollbackConfig_To_v1beta1_RollbackConfig(&in.RollbackTo, &out.RollbackTo, s); err != nil {
return err
}
return nil
}
func Convert_extensions_DeploymentRollback_To_v1beta1_DeploymentRollback(in *extensions.DeploymentRollback, out *DeploymentRollback, s conversion.Scope) error {
return autoConvert_extensions_DeploymentRollback_To_v1beta1_DeploymentRollback(in, out, s)
}
func autoConvert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensions.DeploymentSpec, out *DeploymentSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.DeploymentSpec))(in)
@ -2709,6 +2735,15 @@ func autoConvert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec(in *extensi
return err
}
out.Paused = in.Paused
// unable to generate simple pointer conversion for extensions.RollbackConfig -> v1beta1.RollbackConfig
if in.RollbackTo != nil {
out.RollbackTo = new(RollbackConfig)
if err := Convert_extensions_RollbackConfig_To_v1beta1_RollbackConfig(in.RollbackTo, out.RollbackTo, s); err != nil {
return err
}
} else {
out.RollbackTo = nil
}
return nil
}
@ -3360,6 +3395,18 @@ func Convert_extensions_ReplicationControllerDummy_To_v1beta1_ReplicationControl
return autoConvert_extensions_ReplicationControllerDummy_To_v1beta1_ReplicationControllerDummy(in, out, s)
}
func autoConvert_extensions_RollbackConfig_To_v1beta1_RollbackConfig(in *extensions.RollbackConfig, out *RollbackConfig, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.RollbackConfig))(in)
}
out.Revision = in.Revision
return nil
}
func Convert_extensions_RollbackConfig_To_v1beta1_RollbackConfig(in *extensions.RollbackConfig, out *RollbackConfig, s conversion.Scope) error {
return autoConvert_extensions_RollbackConfig_To_v1beta1_RollbackConfig(in, out, s)
}
func autoConvert_extensions_RollingUpdateDaemonSet_To_v1beta1_RollingUpdateDaemonSet(in *extensions.RollingUpdateDaemonSet, out *RollingUpdateDaemonSet, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*extensions.RollingUpdateDaemonSet))(in)
@ -3806,6 +3853,32 @@ func Convert_v1beta1_DeploymentList_To_extensions_DeploymentList(in *DeploymentL
return autoConvert_v1beta1_DeploymentList_To_extensions_DeploymentList(in, out, s)
}
func autoConvert_v1beta1_DeploymentRollback_To_extensions_DeploymentRollback(in *DeploymentRollback, out *extensions.DeploymentRollback, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*DeploymentRollback))(in)
}
if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
return err
}
out.Name = in.Name
if in.UpdatedAnnotations != nil {
out.UpdatedAnnotations = make(map[string]string)
for key, val := range in.UpdatedAnnotations {
out.UpdatedAnnotations[key] = val
}
} else {
out.UpdatedAnnotations = nil
}
if err := Convert_v1beta1_RollbackConfig_To_extensions_RollbackConfig(&in.RollbackTo, &out.RollbackTo, s); err != nil {
return err
}
return nil
}
func Convert_v1beta1_DeploymentRollback_To_extensions_DeploymentRollback(in *DeploymentRollback, out *extensions.DeploymentRollback, s conversion.Scope) error {
return autoConvert_v1beta1_DeploymentRollback_To_extensions_DeploymentRollback(in, out, s)
}
func autoConvert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *DeploymentSpec, out *extensions.DeploymentSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*DeploymentSpec))(in)
@ -3833,6 +3906,15 @@ func autoConvert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec(in *Deploym
}
// in.UniqueLabelKey has no peer in out
out.Paused = in.Paused
// unable to generate simple pointer conversion for v1beta1.RollbackConfig -> extensions.RollbackConfig
if in.RollbackTo != nil {
out.RollbackTo = new(extensions.RollbackConfig)
if err := Convert_v1beta1_RollbackConfig_To_extensions_RollbackConfig(in.RollbackTo, out.RollbackTo, s); err != nil {
return err
}
} else {
out.RollbackTo = nil
}
return nil
}
@ -4493,6 +4575,18 @@ func Convert_v1beta1_ReplicationControllerDummy_To_extensions_ReplicationControl
return autoConvert_v1beta1_ReplicationControllerDummy_To_extensions_ReplicationControllerDummy(in, out, s)
}
func autoConvert_v1beta1_RollbackConfig_To_extensions_RollbackConfig(in *RollbackConfig, out *extensions.RollbackConfig, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*RollbackConfig))(in)
}
out.Revision = in.Revision
return nil
}
func Convert_v1beta1_RollbackConfig_To_extensions_RollbackConfig(in *RollbackConfig, out *extensions.RollbackConfig, s conversion.Scope) error {
return autoConvert_v1beta1_RollbackConfig_To_extensions_RollbackConfig(in, out, s)
}
func autoConvert_v1beta1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet(in *RollingUpdateDaemonSet, out *extensions.RollingUpdateDaemonSet, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*RollingUpdateDaemonSet))(in)
@ -4742,6 +4836,7 @@ func init() {
autoConvert_extensions_DaemonSetUpdateStrategy_To_v1beta1_DaemonSetUpdateStrategy,
autoConvert_extensions_DaemonSet_To_v1beta1_DaemonSet,
autoConvert_extensions_DeploymentList_To_v1beta1_DeploymentList,
autoConvert_extensions_DeploymentRollback_To_v1beta1_DeploymentRollback,
autoConvert_extensions_DeploymentSpec_To_v1beta1_DeploymentSpec,
autoConvert_extensions_DeploymentStatus_To_v1beta1_DeploymentStatus,
autoConvert_extensions_DeploymentStrategy_To_v1beta1_DeploymentStrategy,
@ -4772,6 +4867,7 @@ func init() {
autoConvert_extensions_ReplicaSetStatus_To_v1beta1_ReplicaSetStatus,
autoConvert_extensions_ReplicaSet_To_v1beta1_ReplicaSet,
autoConvert_extensions_ReplicationControllerDummy_To_v1beta1_ReplicationControllerDummy,
autoConvert_extensions_RollbackConfig_To_v1beta1_RollbackConfig,
autoConvert_extensions_RollingUpdateDaemonSet_To_v1beta1_RollingUpdateDaemonSet,
autoConvert_extensions_RollingUpdateDeployment_To_v1beta1_RollingUpdateDeployment,
autoConvert_extensions_ScaleSpec_To_v1beta1_ScaleSpec,
@ -4837,6 +4933,7 @@ func init() {
autoConvert_v1beta1_DaemonSetUpdateStrategy_To_extensions_DaemonSetUpdateStrategy,
autoConvert_v1beta1_DaemonSet_To_extensions_DaemonSet,
autoConvert_v1beta1_DeploymentList_To_extensions_DeploymentList,
autoConvert_v1beta1_DeploymentRollback_To_extensions_DeploymentRollback,
autoConvert_v1beta1_DeploymentSpec_To_extensions_DeploymentSpec,
autoConvert_v1beta1_DeploymentStatus_To_extensions_DeploymentStatus,
autoConvert_v1beta1_Deployment_To_extensions_Deployment,
@ -4867,6 +4964,7 @@ func init() {
autoConvert_v1beta1_ReplicaSetStatus_To_extensions_ReplicaSetStatus,
autoConvert_v1beta1_ReplicaSet_To_extensions_ReplicaSet,
autoConvert_v1beta1_ReplicationControllerDummy_To_extensions_ReplicationControllerDummy,
autoConvert_v1beta1_RollbackConfig_To_extensions_RollbackConfig,
autoConvert_v1beta1_RollingUpdateDaemonSet_To_extensions_RollingUpdateDaemonSet,
autoConvert_v1beta1_RollingUpdateDeployment_To_extensions_RollingUpdateDeployment,
autoConvert_v1beta1_ScaleSpec_To_extensions_ScaleSpec,

View File

@ -1126,6 +1126,25 @@ func deepCopy_v1beta1_DeploymentList(in DeploymentList, out *DeploymentList, c *
return nil
}
func deepCopy_v1beta1_DeploymentRollback(in DeploymentRollback, out *DeploymentRollback, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
out.Name = in.Name
if in.UpdatedAnnotations != nil {
out.UpdatedAnnotations = make(map[string]string)
for key, val := range in.UpdatedAnnotations {
out.UpdatedAnnotations[key] = val
}
} else {
out.UpdatedAnnotations = nil
}
if err := deepCopy_v1beta1_RollbackConfig(in.RollbackTo, &out.RollbackTo, c); err != nil {
return err
}
return nil
}
func deepCopy_v1beta1_DeploymentSpec(in DeploymentSpec, out *DeploymentSpec, c *conversion.Cloner) error {
if in.Replicas != nil {
out.Replicas = new(int32)
@ -1160,6 +1179,14 @@ func deepCopy_v1beta1_DeploymentSpec(in DeploymentSpec, out *DeploymentSpec, c *
out.UniqueLabelKey = nil
}
out.Paused = in.Paused
if in.RollbackTo != nil {
out.RollbackTo = new(RollbackConfig)
if err := deepCopy_v1beta1_RollbackConfig(*in.RollbackTo, out.RollbackTo, c); err != nil {
return err
}
} else {
out.RollbackTo = nil
}
return nil
}
@ -1633,6 +1660,11 @@ func deepCopy_v1beta1_ReplicationControllerDummy(in ReplicationControllerDummy,
return nil
}
func deepCopy_v1beta1_RollbackConfig(in RollbackConfig, out *RollbackConfig, c *conversion.Cloner) error {
out.Revision = in.Revision
return nil
}
func deepCopy_v1beta1_RollingUpdateDaemonSet(in RollingUpdateDaemonSet, out *RollingUpdateDaemonSet, c *conversion.Cloner) error {
if in.MaxUnavailable != nil {
out.MaxUnavailable = new(intstr.IntOrString)
@ -1858,6 +1890,7 @@ func init() {
deepCopy_v1beta1_DaemonSetUpdateStrategy,
deepCopy_v1beta1_Deployment,
deepCopy_v1beta1_DeploymentList,
deepCopy_v1beta1_DeploymentRollback,
deepCopy_v1beta1_DeploymentSpec,
deepCopy_v1beta1_DeploymentStatus,
deepCopy_v1beta1_DeploymentStrategy,
@ -1888,6 +1921,7 @@ func init() {
deepCopy_v1beta1_ReplicaSetSpec,
deepCopy_v1beta1_ReplicaSetStatus,
deepCopy_v1beta1_ReplicationControllerDummy,
deepCopy_v1beta1_RollbackConfig,
deepCopy_v1beta1_RollingUpdateDaemonSet,
deepCopy_v1beta1_RollingUpdateDeployment,
deepCopy_v1beta1_Scale,

View File

@ -41,6 +41,7 @@ func addKnownTypes(scheme *runtime.Scheme) {
&ClusterAutoscalerList{},
&Deployment{},
&DeploymentList{},
&DeploymentRollback{},
&HorizontalPodAutoscaler{},
&HorizontalPodAutoscalerList{},
&Job{},
@ -66,6 +67,7 @@ func (obj *ClusterAutoscaler) GetObjectKind() unversioned.ObjectKind {
func (obj *ClusterAutoscalerList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Deployment) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *DeploymentRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *HorizontalPodAutoscaler) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *HorizontalPodAutoscalerList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Job) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }

File diff suppressed because it is too large Load Diff

View File

@ -221,6 +221,24 @@ type DeploymentSpec struct {
// Indicates that the deployment is paused and will not be processed by the
// deployment controller.
Paused bool `json:"paused,omitempty"`
// The config this deployment is rolling back to. Will be cleared after rollback is done.
RollbackTo *RollbackConfig `json:"rollbackTo,omitempty"`
}
// DeploymentRollback stores the information required to rollback a deployment.
type DeploymentRollback struct {
unversioned.TypeMeta `json:",inline"`
// Required: This must match the Name of a deployment.
Name string `json:"name"`
// The annotations to be updated to a deployment
UpdatedAnnotations map[string]string `json:"updatedAnnotations,omitempty"`
// The config of this deployment rollback.
RollbackTo RollbackConfig `json:"rollbackTo"`
}
type RollbackConfig struct {
// The revision to rollback to. If set to 0, rollbck to the last revision.
Revision int64 `json:"revision,omitempty"`
}
const (

View File

@ -148,6 +148,17 @@ func (DeploymentList) SwaggerDoc() map[string]string {
return map_DeploymentList
}
var map_DeploymentRollback = map[string]string{
"": "DeploymentRollback stores the information required to rollback a deployment.",
"name": "Required: This must match the Name of a deployment.",
"updatedAnnotations": "The annotations to be updated to a deployment",
"rollbackTo": "The config of this deployment rollback.",
}
func (DeploymentRollback) SwaggerDoc() map[string]string {
return map_DeploymentRollback
}
var map_DeploymentSpec = map[string]string{
"": "DeploymentSpec is the specification of the desired behavior of the Deployment.",
"replicas": "Number of desired pods. This is a pointer to distinguish between explicit zero and not specified. Defaults to 1.",
@ -157,6 +168,7 @@ var map_DeploymentSpec = map[string]string{
"revisionHistoryLimit": "The number of old ReplicationControllers to retain to allow rollback. This is a pointer to distinguish between explicit zero and not specified.",
"uniqueLabelKey": "Key of the selector that is added to existing RCs (and label key that is added to its pods) to prevent the existing RCs to select new pods (and old pods being selected by new RC). Users can set this to an empty string to indicate that the system should not add any selector and label. If unspecified, system uses DefaultDeploymentUniqueLabelKey(\"deployment.kubernetes.io/podTemplateHash\"). Value of this key is hash of DeploymentSpec.PodTemplateSpec. No label is added if this is set to empty string.",
"paused": "Indicates that the deployment is paused and will not be processed by the deployment controller.",
"rollbackTo": "The config this deployment is rolling back to. Will be cleared after rollback is done.",
}
func (DeploymentSpec) SwaggerDoc() map[string]string {
@ -482,6 +494,14 @@ func (ReplicationControllerDummy) SwaggerDoc() map[string]string {
return map_ReplicationControllerDummy
}
var map_RollbackConfig = map[string]string{
"revision": "The revision to rollback to. If set to 0, rollbck to the last revision.",
}
func (RollbackConfig) SwaggerDoc() map[string]string {
return map_RollbackConfig
}
var map_RollingUpdateDaemonSet = map[string]string{
"": "Spec to control the desired behavior of daemon set rolling update.",
"maxUnavailable": "The maximum number of DaemonSet pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of total number of DaemonSet pods at the start of the update (ex: 10%). Absolute number is calculated from percentage by rounding up. This cannot be 0. Default value is 1. Example: when this is set to 30%, 30% of the currently running DaemonSet pods can be stopped for an update at any given time. The update starts by stopping at most 30% of the currently running DaemonSet pods and then brings up new DaemonSet pods in their place. Once the new pods are ready, it then proceeds onto other DaemonSet pods, thus ensuring that at least 70% of original number of DaemonSet pods are available at all times during the update.",

View File

@ -308,6 +308,13 @@ func ValidateDeploymentStrategy(strategy *extensions.DeploymentStrategy, fldPath
return allErrs
}
func ValidateRollback(rollback *extensions.RollbackConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
v := rollback.Revision
allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(v), fldPath.Child("version"))...)
return allErrs
}
// Validates given deployment spec.
func ValidateDeploymentSpec(spec *extensions.DeploymentSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
@ -323,6 +330,9 @@ func ValidateDeploymentSpec(spec *extensions.DeploymentSpec, fldPath *field.Path
if len(spec.UniqueLabelKey) > 0 {
allErrs = append(allErrs, apivalidation.ValidateLabelName(spec.UniqueLabelKey, fldPath.Child("uniqueLabel"))...)
}
if spec.RollbackTo != nil {
allErrs = append(allErrs, ValidateRollback(spec.RollbackTo, fldPath.Child("rollback"))...)
}
return allErrs
}
@ -338,6 +348,15 @@ func ValidateDeployment(obj *extensions.Deployment) field.ErrorList {
return allErrs
}
func ValidateDeploymentRollback(obj *extensions.DeploymentRollback) field.ErrorList {
allErrs := apivalidation.ValidateAnnotations(obj.UpdatedAnnotations, field.NewPath("updatedAnnotations"))
if len(obj.Name) == 0 {
allErrs = append(allErrs, field.Required(field.NewPath("name"), "name is required"))
}
allErrs = append(allErrs, ValidateRollback(&obj.RollbackTo, field.NewPath("rollback"))...)
return allErrs
}
func ValidateThirdPartyResourceDataUpdate(update, old *extensions.ThirdPartyResourceData) field.ErrorList {
return ValidateThirdPartyResourceData(update)
}

View File

@ -872,6 +872,9 @@ func validDeployment() *extensions.Deployment {
},
},
UniqueLabelKey: "my-label",
RollbackTo: &extensions.RollbackConfig{
Revision: 1,
},
},
}
}
@ -948,6 +951,11 @@ func TestValidateDeployment(t *testing.T) {
}
errorCases["must not be greater than 100%"] = invalidMaxUnavailableDeployment
// Rollback.Revision must be non-negative
invalidRollbackRevisionDeployment := validDeployment()
invalidRollbackRevisionDeployment.Spec.RollbackTo.Revision = -3
errorCases["must be greater than or equal to 0"] = invalidRollbackRevisionDeployment
for k, v := range errorCases {
errs := ValidateDeployment(v)
if len(errs) == 0 {
@ -958,6 +966,46 @@ func TestValidateDeployment(t *testing.T) {
}
}
func validDeploymentRollback() *extensions.DeploymentRollback {
return &extensions.DeploymentRollback{
Name: "abc",
UpdatedAnnotations: map[string]string{
"created-by": "abc",
},
RollbackTo: extensions.RollbackConfig{
Revision: 1,
},
}
}
func TestValidateDeploymentRollback(t *testing.T) {
noAnnotation := validDeploymentRollback()
noAnnotation.UpdatedAnnotations = nil
successCases := []*extensions.DeploymentRollback{
validDeploymentRollback(),
noAnnotation,
}
for _, successCase := range successCases {
if errs := ValidateDeploymentRollback(successCase); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
}
errorCases := map[string]*extensions.DeploymentRollback{}
invalidNoName := validDeploymentRollback()
invalidNoName.Name = ""
errorCases["name: Required value"] = invalidNoName
for k, v := range errorCases {
errs := ValidateDeploymentRollback(v)
if len(errs) == 0 {
t.Errorf("[%s] expected failure", k)
} else if !strings.Contains(errs[0].Error(), k) {
t.Errorf("unexpected error: %q, expected: %q", errs[0].Error(), k)
}
}
}
func TestValidateJob(t *testing.T) {
validSelector := &extensions.LabelSelector{
MatchLabels: map[string]string{"a": "b"},

View File

@ -36,6 +36,7 @@ type DeploymentInterface interface {
Update(*extensions.Deployment) (*extensions.Deployment, error)
UpdateStatus(*extensions.Deployment) (*extensions.Deployment, error)
Watch(opts api.ListOptions) (watch.Interface, error)
Rollback(*extensions.DeploymentRollback) error
}
// deployments implements DeploymentInterface
@ -100,3 +101,8 @@ func (c *deployments) Watch(opts api.ListOptions) (watch.Interface, error) {
VersionedParams(&opts, api.ParameterCodec).
Watch()
}
// Rollback applied the provided DeploymentRollback to the named deployment in the current namespace.
func (c *deployments) Rollback(deploymentRollback *extensions.DeploymentRollback) error {
return c.client.Post().Namespace(c.ns).Resource("deployments").Name(deploymentRollback.Name).SubResource("rollback").Body(deploymentRollback).Do().Error()
}

View File

@ -33,7 +33,7 @@ import (
"k8s.io/kubernetes/pkg/labels"
)
func getDeploymentsResoureName() string {
func getDeploymentsResourceName() string {
return "deployments"
}
@ -48,7 +48,7 @@ func TestDeploymentCreate(t *testing.T) {
c := &simple.Client{
Request: simple.Request{
Method: "POST",
Path: testapi.Extensions.ResourcePath(getDeploymentsResoureName(), ns, ""),
Path: testapi.Extensions.ResourcePath(getDeploymentsResourceName(), ns, ""),
Query: simple.BuildQueryValues(nil),
Body: &deployment,
},
@ -74,7 +74,7 @@ func TestDeploymentGet(t *testing.T) {
c := &simple.Client{
Request: simple.Request{
Method: "GET",
Path: testapi.Extensions.ResourcePath(getDeploymentsResoureName(), ns, "abc"),
Path: testapi.Extensions.ResourcePath(getDeploymentsResourceName(), ns, "abc"),
Query: simple.BuildQueryValues(nil),
Body: nil,
},
@ -101,7 +101,7 @@ func TestDeploymentList(t *testing.T) {
c := &simple.Client{
Request: simple.Request{
Method: "GET",
Path: testapi.Extensions.ResourcePath(getDeploymentsResoureName(), ns, ""),
Path: testapi.Extensions.ResourcePath(getDeploymentsResourceName(), ns, ""),
Query: simple.BuildQueryValues(nil),
Body: nil,
},
@ -124,7 +124,7 @@ func TestDeploymentUpdate(t *testing.T) {
c := &simple.Client{
Request: simple.Request{
Method: "PUT",
Path: testapi.Extensions.ResourcePath(getDeploymentsResoureName(), ns, "abc"),
Path: testapi.Extensions.ResourcePath(getDeploymentsResourceName(), ns, "abc"),
Query: simple.BuildQueryValues(nil),
},
Response: simple.Response{StatusCode: 200, Body: deployment},
@ -146,7 +146,7 @@ func TestDeploymentUpdateStatus(t *testing.T) {
c := &simple.Client{
Request: simple.Request{
Method: "PUT",
Path: testapi.Extensions.ResourcePath(getDeploymentsResoureName(), ns, "abc") + "/status",
Path: testapi.Extensions.ResourcePath(getDeploymentsResourceName(), ns, "abc") + "/status",
Query: simple.BuildQueryValues(nil),
},
Response: simple.Response{StatusCode: 200, Body: deployment},
@ -161,7 +161,7 @@ func TestDeploymentDelete(t *testing.T) {
c := &simple.Client{
Request: simple.Request{
Method: "DELETE",
Path: testapi.Extensions.ResourcePath(getDeploymentsResoureName(), ns, "foo"),
Path: testapi.Extensions.ResourcePath(getDeploymentsResourceName(), ns, "foo"),
Query: simple.BuildQueryValues(nil),
},
Response: simple.Response{StatusCode: 200},
@ -175,7 +175,7 @@ func TestDeploymentWatch(t *testing.T) {
c := &simple.Client{
Request: simple.Request{
Method: "GET",
Path: testapi.Extensions.ResourcePathWithPrefix("watch", getDeploymentsResoureName(), "", ""),
Path: testapi.Extensions.ResourcePathWithPrefix("watch", getDeploymentsResourceName(), "", ""),
Query: url.Values{"resourceVersion": []string{}},
},
Response: simple.Response{StatusCode: 200},
@ -217,3 +217,24 @@ func TestListDeploymentsLabels(t *testing.T) {
receivedPodList, err := c.Deployments(ns).List(options)
c.Validate(t, receivedPodList, err)
}
func TestDeploymentRollback(t *testing.T) {
ns := api.NamespaceDefault
deploymentRollback := &extensions.DeploymentRollback{
Name: "abc",
UpdatedAnnotations: map[string]string{},
RollbackTo: extensions.RollbackConfig{Revision: 1},
}
c := &simple.Client{
Request: simple.Request{
Method: "POST",
Path: testapi.Extensions.ResourcePath(getDeploymentsResourceName(), ns, "abc") + "/rollback",
Query: simple.BuildQueryValues(nil),
Body: deploymentRollback,
},
Response: simple.Response{StatusCode: http.StatusOK},
}
err := c.Setup(t).Deployments(ns).Rollback(deploymentRollback)
defer c.Close()
c.ValidateCommon(t, err)
}

View File

@ -92,3 +92,14 @@ func (c *FakeDeployments) Delete(name string, options *api.DeleteOptions) error
func (c *FakeDeployments) Watch(opts api.ListOptions) (watch.Interface, error) {
return c.Fake.InvokesWatch(NewWatchAction("deployments", c.Namespace, opts))
}
func (c *FakeDeployments) Rollback(deploymentRollback *extensions.DeploymentRollback) error {
action := CreateActionImpl{}
action.Verb = "create"
action.Resource = "deployments"
action.Subresource = "rollback"
action.Object = deploymentRollback
_, err := c.Fake.Invokes(action, deploymentRollback)
return err
}

View File

@ -19,6 +19,7 @@ package deployment
import (
"fmt"
"math"
"reflect"
"sort"
"strconv"
"time"
@ -417,6 +418,13 @@ func (dc *DeploymentController) syncDeployment(key string) error {
glog.V(4).Infof("Ignoring paused deployment %s/%s", d.Namespace, d.Name)
return nil
}
if d.Spec.RollbackTo != nil {
revision := d.Spec.RollbackTo.Revision
if _, err = dc.rollback(&d, &revision); err != nil {
return err
}
}
switch d.Spec.Strategy.Type {
case extensions.RecreateDeploymentStrategyType:
return dc.syncRecreateDeployment(d)
@ -426,6 +434,59 @@ func (dc *DeploymentController) syncDeployment(key string) error {
return fmt.Errorf("unexpected deployment strategy type: %s", d.Spec.Strategy.Type)
}
// Rolling back to a revision; no-op if the toRevision is deployment's current revision
func (dc *DeploymentController) rollback(deployment *extensions.Deployment, toRevision *int64) (*extensions.Deployment, error) {
newRC, allOldRCs, err := dc.getNewRCAndAllOldRCs(*deployment)
if err != nil {
return nil, err
}
allRCs := append(allOldRCs, newRC)
// If rollback revision is 0, rollback to the last revision
if *toRevision == 0 {
if *toRevision = lastRevision(allRCs); *toRevision == 0 {
// If we still can't find the last revision, gives up rollback
dc.emitRollbackWarningEvent(deployment, "DeploymentRollbackRevisionNotFound", "Unable to find last revision.")
// Gives up rollback
return dc.updateDeploymentAndClearRollbackTo(deployment)
}
}
for _, rc := range allRCs {
v, err := revision(rc)
if err != nil {
glog.V(4).Infof("Unable to extract revision from deployment's rc %q: %v", rc.Name, err)
continue
}
if v == *toRevision {
glog.V(4).Infof("Found rc %q with desired revision %d", rc.Name, v)
// rollback by copying podTemplate.Spec from the rc, and increment revision number by 1
// no-op if the the spec matches current deployment's podTemplate.Spec
deployment, performedRollback, err := dc.rollbackToTemplate(deployment, rc)
if performedRollback && err == nil {
dc.emitRollbackNormalEvent(deployment, fmt.Sprintf("Rolled back deployment %q to revision %d", deployment.Name, *toRevision))
}
return deployment, err
}
}
dc.emitRollbackWarningEvent(deployment, "DeploymentRollbackRevisionNotFound", "Unable to find the revision to rollback to.")
// Gives up rollback
return dc.updateDeploymentAndClearRollbackTo(deployment)
}
func (dc *DeploymentController) emitRollbackWarningEvent(deployment *extensions.Deployment, reason, message string) {
dc.eventRecorder.Eventf(deployment, api.EventTypeWarning, reason, message)
}
func (dc *DeploymentController) emitRollbackNormalEvent(deployment *extensions.Deployment, message string) {
dc.eventRecorder.Eventf(deployment, api.EventTypeNormal, "DeploymentRollback", message)
}
// updateDeploymentAndClearRollbackTo sets .spec.rollbackTo to nil and update the input deployment
func (dc *DeploymentController) updateDeploymentAndClearRollbackTo(deployment *extensions.Deployment) (*extensions.Deployment, error) {
glog.V(4).Infof("Cleans up rollbackTo of deployment %s", deployment.Name)
deployment.Spec.RollbackTo = nil
return dc.updateDeployment(deployment)
}
func (dc *DeploymentController) syncRecreateDeployment(deployment extensions.Deployment) error {
newRC, oldRCs, err := dc.getNewRCAndOldRCs(deployment)
if err != nil {
@ -514,7 +575,9 @@ func (dc *DeploymentController) syncDeploymentStatus(allRCs []*api.ReplicationCo
return nil
}
func (dc *DeploymentController) getNewRCAndOldRCs(deployment extensions.Deployment) (*api.ReplicationController, []*api.ReplicationController, error) {
// getNewRCAndMaybeFilteredOldRCs returns new RC and old RCs of the deployment. If ignoreNoPod is true,
// the returned old RCs won't include the ones with no pods; otherwise, all old RCs will be returned.
func (dc *DeploymentController) getNewRCAndMaybeFilteredOldRCs(deployment extensions.Deployment, ignoreNoPod bool) (*api.ReplicationController, []*api.ReplicationController, error) {
oldRCs, allOldRCs, err := dc.getOldRCs(deployment)
if err != nil {
return nil, nil, err
@ -535,19 +598,35 @@ func (dc *DeploymentController) getNewRCAndOldRCs(deployment extensions.Deployme
glog.V(4).Infof("Error: %v. Unable to update deployment revision, will retry later.", err)
}
}
if !ignoreNoPod {
return newRC, allOldRCs, nil
}
return newRC, oldRCs, nil
}
func revision(rc *api.ReplicationController) (int, error) {
// getNewRCAndOldRCs returns new RC and old RCs of the deployment.
// Note that the returned old RCs don't include the ones with no pods.
func (dc *DeploymentController) getNewRCAndOldRCs(deployment extensions.Deployment) (*api.ReplicationController, []*api.ReplicationController, error) {
return dc.getNewRCAndMaybeFilteredOldRCs(deployment, true)
}
// getNewRCAndAllOldRCs returns new RC and old RCs of the deployment.
// Note that all old RCs are returned, include the ones with no pods.
func (dc *DeploymentController) getNewRCAndAllOldRCs(deployment extensions.Deployment) (*api.ReplicationController, []*api.ReplicationController, error) {
return dc.getNewRCAndMaybeFilteredOldRCs(deployment, false)
}
func revision(rc *api.ReplicationController) (int64, error) {
v, ok := rc.Annotations[deploymentutil.RevisionAnnotation]
if !ok {
return 0, nil
}
return strconv.Atoi(v)
return strconv.ParseInt(v, 10, 64)
}
func maxRevision(allRCs []*api.ReplicationController) int {
max := 0
func maxRevision(allRCs []*api.ReplicationController) int64 {
max := int64(0)
for _, rc := range allRCs {
if v, err := revision(rc); err != nil {
// Skip the RCs when it failed to parse their revision information
@ -559,6 +638,25 @@ func maxRevision(allRCs []*api.ReplicationController) int {
return max
}
// lastRevision finds the second max revision number in all RCs (the last revision)
func lastRevision(allRCs []*api.ReplicationController) int64 {
max, secMax := int64(0), int64(0)
for _, rc := range allRCs {
if v, err := revision(rc); err != nil {
// Skip the RCs when it failed to parse their revision information
glog.V(4).Infof("Error: %v. Couldn't parse revision for rc %#v, deployment controller will skip it when reconciling revisions.", err, rc)
} else if v >= max {
secMax = max
max = v
} else if v > secMax {
secMax = v
}
}
return secMax
}
// getOldRCs returns two sets of old RCs of the deployment. The first set of old RCs doesn't include
// the ones with no pods, and the second set of old RCs include all old RCs.
func (dc *DeploymentController) getOldRCs(deployment extensions.Deployment) ([]*api.ReplicationController, []*api.ReplicationController, error) {
return deploymentutil.GetOldRCsFromLists(deployment, dc.client,
func(namespace string, options api.ListOptions) (*api.PodList, error) {
@ -573,9 +671,9 @@ func (dc *DeploymentController) getOldRCs(deployment extensions.Deployment) ([]*
// Returns an RC that matches the intent of the given deployment.
// It creates a new RC if required.
// The revision of the new RC will be updated to maxOldRevision + 1
func (dc *DeploymentController) getNewRC(deployment extensions.Deployment, maxOldRevision int) (*api.ReplicationController, error) {
func (dc *DeploymentController) getNewRC(deployment extensions.Deployment, maxOldRevision int64) (*api.ReplicationController, error) {
// Calculate revision number for this new RC
newRevision := strconv.Itoa(maxOldRevision + 1)
newRevision := strconv.FormatInt(maxOldRevision+1, 10)
existingNewRC, err := deploymentutil.GetNewRCFromList(deployment, dc.client,
func(namespace string, options api.ListOptions) ([]api.ReplicationController, error) {
@ -589,7 +687,7 @@ func (dc *DeploymentController) getNewRC(deployment extensions.Deployment, maxOl
}
if existingNewRC.Annotations[deploymentutil.RevisionAnnotation] != newRevision {
existingNewRC.Annotations[deploymentutil.RevisionAnnotation] = newRevision
glog.V(4).Infof("update existingNewRC's revision to %s - %+v\n", newRevision, existingNewRC)
glog.V(4).Infof("update existingNewRC %s revision to %s - %+v\n", existingNewRC.Name, newRevision)
return dc.client.ReplicationControllers(deployment.ObjectMeta.Namespace).Update(existingNewRC)
}
return existingNewRC, nil
@ -636,11 +734,8 @@ func (dc *DeploymentController) getNewRC(deployment extensions.Deployment, maxOl
return nil, fmt.Errorf("error creating replication controller: %v", err)
}
if err = dc.updateDeploymentRevision(deployment, newRevision); err != nil {
return createdRC, err
}
return createdRC, nil
err = dc.updateDeploymentRevision(deployment, newRevision)
return createdRC, err
}
func (dc *DeploymentController) updateDeploymentRevision(deployment extensions.Deployment, revision string) error {
@ -652,6 +747,15 @@ func (dc *DeploymentController) updateDeploymentRevision(deployment extensions.D
return err
}
func (dc *DeploymentController) updateRCRevision(rc api.ReplicationController, revision string) error {
if rc.Annotations == nil {
rc.Annotations = make(map[string]string)
}
rc.Annotations[deploymentutil.RevisionAnnotation] = revision
_, err := dc.client.ReplicationControllers(rc.ObjectMeta.Namespace).Update(&rc)
return err
}
func (dc *DeploymentController) reconcileNewRC(allRCs []*api.ReplicationController, newRC *api.ReplicationController, deployment extensions.Deployment) (bool, error) {
if newRC.Spec.Replicas == deployment.Spec.Replicas {
// Scaling not required.
@ -861,3 +965,16 @@ func (dc *DeploymentController) updateDeployment(deployment *extensions.Deployme
// TODO: Using client for now, update to use store when it is ready.
return dc.expClient.Deployments(deployment.ObjectMeta.Namespace).Update(deployment)
}
func (dc *DeploymentController) rollbackToTemplate(deployment *extensions.Deployment, rc *api.ReplicationController) (d *extensions.Deployment, performedRollback bool, err error) {
if !reflect.DeepEqual(deploymentutil.GetNewRCTemplate(*deployment), *rc.Spec.Template) {
glog.Infof("Rolling back deployment %s to template spec %+v", deployment.Name, rc.Spec.Template.Spec)
deploymentutil.SetFromRCTemplate(deployment, *rc.Spec.Template)
performedRollback = true
} else {
glog.V(4).Infof("Rolling back to a revision that contains the same template as current deployment %s, skipping rollback...", deployment.Name)
dc.emitRollbackWarningEvent(deployment, "DeploymentRollbackTemplateUnchanged", fmt.Sprintf("The rollback revision contains the same template as current deployment %q", deployment.Name))
}
d, err = dc.updateDeploymentAndClearRollbackTo(deployment)
return
}

View File

@ -622,6 +622,7 @@ func (m *Master) getExtensionResources(c *Config) map[string]rest.Storage {
storage["deployments"] = deploymentStorage.Deployment
storage["deployments/status"] = deploymentStorage.Status
storage["deployments/scale"] = deploymentStorage.Scale
storage["deployments/rollback"] = deploymentStorage.Rollback
}
if isEnabled("jobs") {
jobStorage, jobStatusStorage := jobetcd.NewREST(dbClient("jobs"), storageDecorator)

View File

@ -21,8 +21,10 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
etcderr "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/apis/extensions"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/deployment"
@ -30,8 +32,6 @@ import (
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
)
// DeploymentStorage includes dummy storage for Deployments and for Scale subresource.
@ -39,16 +39,18 @@ type DeploymentStorage struct {
Deployment *REST
Status *StatusREST
Scale *ScaleREST
Rollback *RollbackREST
}
func NewStorage(s storage.Interface, storageDecorator generic.StorageDecorator) DeploymentStorage {
deploymentRest, deploymentStatusRest := NewREST(s, storageDecorator)
deploymentRest, deploymentStatusRest, deploymentRollbackRest := NewREST(s, storageDecorator)
deploymentRegistry := deployment.NewRegistry(deploymentRest)
return DeploymentStorage{
Deployment: deploymentRest,
Status: deploymentStatusRest,
Scale: &ScaleREST{registry: &deploymentRegistry},
Rollback: deploymentRollbackRest,
}
}
@ -57,7 +59,7 @@ type REST struct {
}
// NewREST returns a RESTStorage object that will work against deployments.
func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator) (*REST, *StatusREST) {
func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator) (*REST, *StatusREST, *RollbackREST) {
prefix := "/deployments"
newListFunc := func() runtime.Object { return &extensions.DeploymentList{} }
@ -98,7 +100,7 @@ func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator) (*R
}
statusStore := *store
statusStore.UpdateStrategy = deployment.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}
return &REST{store}, &StatusREST{store: &statusStore}, &RollbackREST{store: store}
}
// StatusREST implements the REST endpoint for changing the status of a deployment
@ -115,6 +117,67 @@ func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object
return r.store.Update(ctx, obj)
}
// RollbackREST implements the REST endpoint for initiating the rollback of a deployment
type RollbackREST struct {
store *etcdgeneric.Etcd
}
// New creates a rollback
func (r *RollbackREST) New() runtime.Object {
return &extensions.DeploymentRollback{}
}
var _ = rest.Creater(&RollbackREST{})
func (r *RollbackREST) Create(ctx api.Context, obj runtime.Object) (out runtime.Object, err error) {
rollback, ok := obj.(*extensions.DeploymentRollback)
if !ok {
return nil, fmt.Errorf("expected input object type to be DeploymentRollback, but %T", obj)
}
if errs := extvalidation.ValidateDeploymentRollback(rollback); len(errs) != 0 {
return nil, errors.NewInvalid(extensions.Kind("DeploymentRollback"), rollback.Name, errs)
}
// Update the Deployment with information in DeploymentRollback to trigger rollback
err = r.rollbackDeployment(ctx, rollback.Name, &rollback.RollbackTo, rollback.UpdatedAnnotations)
return
}
func (r *RollbackREST) rollbackDeployment(ctx api.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string) (err error) {
if _, err = r.setDeploymentRollback(ctx, deploymentID, config, annotations); err != nil {
err = etcderr.InterpretGetError(err, extensions.Resource("deployments"), deploymentID)
err = etcderr.InterpretUpdateError(err, extensions.Resource("deployments"), deploymentID)
if _, ok := err.(*errors.StatusError); !ok {
err = errors.NewConflict(extensions.Resource("deployments/rollback"), deploymentID, err)
}
}
return
}
func (r *RollbackREST) setDeploymentRollback(ctx api.Context, deploymentID string, config *extensions.RollbackConfig, annotations map[string]string) (finalDeployment *extensions.Deployment, err error) {
dKey, err := r.store.KeyFunc(ctx, deploymentID)
if err != nil {
return nil, err
}
err = r.store.Storage.GuaranteedUpdate(ctx, dKey, &extensions.Deployment{}, false, storage.SimpleUpdate(func(obj runtime.Object) (runtime.Object, error) {
d, ok := obj.(*extensions.Deployment)
if !ok {
return nil, fmt.Errorf("unexpected object: %#v", obj)
}
if d.Annotations == nil {
d.Annotations = make(map[string]string)
}
for k, v := range annotations {
d.Annotations[k] = v
}
d.Spec.RollbackTo = config
finalDeployment = d
return d, nil
}))
return finalDeployment, err
}
type ScaleREST struct {
registry *deployment.Registry
}

88
pkg/registry/deployment/etcd/etcd_test.go Executable file → Normal file
View File

@ -17,9 +17,12 @@ limitations under the License.
package etcd
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
etcderrors "k8s.io/kubernetes/pkg/api/errors/etcd"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
@ -278,3 +281,88 @@ func TestStatusUpdate(t *testing.T) {
t.Errorf("we expected .status.replicas to be updated to 100 but it was %v", deployment.Status.Replicas)
}
}
func TestEtcdCreateDeploymentRollback(t *testing.T) {
ctx := api.WithNamespace(api.NewContext(), namespace)
testCases := map[string]struct {
rollback extensions.DeploymentRollback
errOK func(error) bool
}{
"normal": {
rollback: extensions.DeploymentRollback{
Name: name,
UpdatedAnnotations: map[string]string{},
RollbackTo: extensions.RollbackConfig{Revision: 1},
},
errOK: func(err error) bool { return err == nil },
},
"noAnnotation": {
rollback: extensions.DeploymentRollback{
Name: name,
RollbackTo: extensions.RollbackConfig{Revision: 1},
},
errOK: func(err error) bool { return err == nil },
},
"noName": {
rollback: extensions.DeploymentRollback{
UpdatedAnnotations: map[string]string{},
RollbackTo: extensions.RollbackConfig{Revision: 1},
},
errOK: func(err error) bool { return err != nil },
},
}
for k, test := range testCases {
storage, server := newStorage(t)
rollbackStorage := storage.Rollback
key, _ := storage.Deployment.KeyFunc(ctx, name)
key = etcdtest.AddPrefix(key)
if _, err := storage.Deployment.Create(ctx, validNewDeployment()); err != nil {
t.Fatalf("%s: unexpected error: %v", k, err)
}
if _, err := rollbackStorage.Create(ctx, &test.rollback); !test.errOK(err) {
t.Errorf("%s: unexpected error: %v", k, err)
} else if err == nil {
// If rollback succeeded, verify Rollback field of deployment
d, err := storage.Deployment.Get(ctx, validNewDeployment().ObjectMeta.Name)
if err != nil {
t.Errorf("%s: unexpected error: %v", k, err)
} else if !reflect.DeepEqual(*d.(*extensions.Deployment).Spec.RollbackTo, test.rollback.RollbackTo) {
t.Errorf("%s: expected: %v, got: %v", k, *d.(*extensions.Deployment).Spec.RollbackTo, test.rollback.RollbackTo)
}
}
server.Terminate(t)
}
}
// Ensure that when a deploymentRollback is created for a deployment that has already been deleted
// by the API server, API server returns not-found error.
func TestEtcdCreateDeploymentRollbackNoDeployment(t *testing.T) {
storage, server := newStorage(t)
defer server.Terminate(t)
rollbackStorage := storage.Rollback
ctx := api.WithNamespace(api.NewContext(), namespace)
key, _ := storage.Deployment.KeyFunc(ctx, name)
key = etcdtest.AddPrefix(key)
_, err := rollbackStorage.Create(ctx, &extensions.DeploymentRollback{
Name: name,
UpdatedAnnotations: map[string]string{},
RollbackTo: extensions.RollbackConfig{Revision: 1},
})
if err == nil {
t.Fatalf("Expected not-found-error but got nothing")
}
if !errors.IsNotFound(etcderrors.InterpretGetError(err, extensions.Resource("deployments"), name)) {
t.Fatalf("Unexpected error returned: %#v", err)
}
_, err = storage.Deployment.Get(ctx, name)
if err == nil {
t.Fatalf("Expected not-found-error but got nothing")
}
if !errors.IsNotFound(etcderrors.InterpretGetError(err, extensions.Resource("deployments"), name)) {
t.Fatalf("Unexpected error: %v", err)
}
}

View File

@ -34,6 +34,7 @@ const (
)
// GetOldRCs returns the old RCs targeted by the given Deployment; get PodList and RCList from client interface.
// Note that the first set of old RCs doesn't include the ones with no pods, and the second set of old RCs include all old RCs.
func GetOldRCs(deployment extensions.Deployment, c client.Interface) ([]*api.ReplicationController, []*api.ReplicationController, error) {
return GetOldRCsFromLists(deployment, c,
func(namespace string, options api.ListOptions) (*api.PodList, error) {
@ -45,7 +46,8 @@ func GetOldRCs(deployment extensions.Deployment, c client.Interface) ([]*api.Rep
})
}
// GetOldRCsFromLists returns the old RCs targeted by the given Deployment; get PodList and RCList with input functions.
// GetOldRCsFromLists returns two sets of old RCs targeted by the given Deployment; get PodList and RCList with input functions.
// Note that the first set of old RCs doesn't include the ones with no pods, and the second set of old RCs include all old RCs.
func GetOldRCsFromLists(deployment extensions.Deployment, c client.Interface, getPodList func(string, api.ListOptions) (*api.PodList, error), getRcList func(string, api.ListOptions) ([]api.ReplicationController, error)) ([]*api.ReplicationController, []*api.ReplicationController, error) {
namespace := deployment.ObjectMeta.Namespace
// 1. Find all pods whose labels match deployment.Spec.Selector
@ -135,6 +137,16 @@ func GetNewRCTemplate(deployment extensions.Deployment) api.PodTemplateSpec {
return newRCTemplate
}
// SetTemplate sets the desired PodTemplateSpec from an RC template to the given deployment.
func SetFromRCTemplate(deployment *extensions.Deployment, template api.PodTemplateSpec) *extensions.Deployment {
deployment.Spec.Template.ObjectMeta = template.ObjectMeta
deployment.Spec.Template.Spec = template.Spec
deployment.Spec.Template.ObjectMeta.Labels = labelsutil.CloneAndRemoveLabel(
deployment.Spec.Template.ObjectMeta.Labels,
deployment.Spec.UniqueLabelKey)
return deployment
}
// Returns the sum of Replicas of the given replication controllers.
func GetReplicaCountForRCs(replicationControllers []*api.ReplicationController) int {
totalReplicaCount := 0

View File

@ -35,3 +35,19 @@ func CloneAndAddLabel(labels map[string]string, labelKey string, labelValue uint
newLabels[labelKey] = fmt.Sprintf("%d", labelValue)
return newLabels
}
// CloneAndRemoveLabel clones the given map and returns a new map with the given key removed.
// Returns the given map, if labelKey is empty.
func CloneAndRemoveLabel(labels map[string]string, labelKey string) map[string]string {
if labelKey == "" {
// Dont need to add a label.
return labels
}
// Clone.
newLabels := map[string]string{}
for key, value := range labels {
newLabels[key] = value
}
delete(newLabels, labelKey)
return newLabels
}

View File

@ -22,6 +22,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/labels"
deploymentutil "k8s.io/kubernetes/pkg/util/deployment"
"k8s.io/kubernetes/pkg/util/intstr"
@ -54,6 +55,12 @@ var _ = Describe("Deployment [Feature:Deployment]", func() {
It("paused deployment should be ignored by the controller", func() {
testPausedDeployment(f)
})
It("deployment should support rollback", func() {
testRollbackDeployment(f)
})
It("deployment should support rollback when there's RC with no revision", func() {
testRollbackDeploymentRCNoRevision(f)
})
})
func newRC(rcName string, replicas int, rcPodLabels map[string]string, imageName string, image string) *api.ReplicationController {
@ -111,6 +118,37 @@ func newDeployment(deploymentName string, replicas int, podLabels map[string]str
}
}
func newDeploymentRollback(name string, annotations map[string]string, revision int64) *extensions.DeploymentRollback {
return &extensions.DeploymentRollback{
Name: name,
UpdatedAnnotations: annotations,
RollbackTo: extensions.RollbackConfig{Revision: revision},
}
}
// checkDeploymentRevision checks if the input deployment's and its new RC's revision and images are as expected.
func checkDeploymentRevision(c *client.Client, ns, deploymentName, revision, imageName, image string) (*extensions.Deployment, *api.ReplicationController) {
deployment, err := c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
// Check revision of the new RC of this deployment
newRC, err := deploymentutil.GetNewRC(*deployment, c)
Expect(err).NotTo(HaveOccurred())
Expect(newRC.Annotations).NotTo(Equal(nil))
Expect(newRC.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal(revision))
// Check revision of This deployment
Expect(deployment.Annotations).NotTo(Equal(nil))
Expect(deployment.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal(revision))
if len(imageName) > 0 {
// Check the image the new RC creates
Expect(newRC.Spec.Template.Spec.Containers[0].Name).Should(Equal(imageName))
Expect(newRC.Spec.Template.Spec.Containers[0].Image).Should(Equal(image))
// Check the image the deployment creates
Expect(deployment.Spec.Template.Spec.Containers[0].Name).Should(Equal(imageName))
Expect(deployment.Spec.Template.Spec.Containers[0].Image).Should(Equal(image))
}
return deployment, newRC
}
func testNewDeployment(f *Framework) {
ns := f.Namespace.Name
c := f.Client
@ -145,14 +183,9 @@ func testNewDeployment(f *Framework) {
Expect(err).NotTo(HaveOccurred())
Expect(deployment.Status.Replicas).Should(Equal(replicas))
Expect(deployment.Status.UpdatedReplicas).Should(Equal(replicas))
// The new RC of this deployment should be revision 1
newRC, err := deploymentutil.GetNewRC(*deployment, c)
Expect(err).NotTo(HaveOccurred())
Expect(newRC.Annotations).NotTo(Equal(nil))
Expect(newRC.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("1"))
// This deployment should be revision 1
Expect(deployment.Annotations).NotTo(Equal(nil))
Expect(deployment.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("1"))
// Check if it's updated to revision 1 correctly
checkDeploymentRevision(c, ns, deploymentName, "1", "nginx", "nginx")
}
func testRollingUpdateDeployment(f *Framework) {
@ -183,7 +216,7 @@ func testRollingUpdateDeployment(f *Framework) {
// Create a deployment to delete nginx pods and instead bring up redis pods.
deploymentName := "redis-deployment"
Logf("Creating deployment %s", deploymentName)
deployment, err := c.Deployments(ns).Create(newDeployment(deploymentName, replicas, deploymentPodLabels, "redis", "redis", extensions.RollingUpdateDeploymentStrategyType, nil))
_, err = c.Deployments(ns).Create(newDeployment(deploymentName, replicas, deploymentPodLabels, "redis", "redis", extensions.RollingUpdateDeploymentStrategyType, nil))
Expect(err).NotTo(HaveOccurred())
defer func() {
deployment, err := c.Deployments(ns).Get(deploymentName)
@ -199,16 +232,8 @@ func testRollingUpdateDeployment(f *Framework) {
err = waitForDeploymentStatus(c, ns, deploymentName, replicas, replicas-1, replicas+1, 0)
Expect(err).NotTo(HaveOccurred())
// The new RC of this deployment should be revision 1
newRC, err := deploymentutil.GetNewRC(*deployment, c)
Expect(err).NotTo(HaveOccurred())
Expect(newRC.Annotations).NotTo(Equal(nil))
Expect(newRC.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("1"))
// This deployment should be revision 1
deployment, err = c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
Expect(deployment.Annotations).NotTo(Equal(nil))
Expect(deployment.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("1"))
// Check if it's updated to revision 1 correctly
checkDeploymentRevision(c, ns, deploymentName, "1", "redis", "redis")
}
func testRollingUpdateDeploymentEvents(f *Framework) {
@ -223,7 +248,7 @@ func testRollingUpdateDeploymentEvents(f *Framework) {
rcName := "nginx-controller"
replicas := 1
rcRevision := "3"
rcRevision := "3546343826724305832"
annotations := make(map[string]string)
annotations[deploymentutil.RevisionAnnotation] = rcRevision
rc := newRC(rcName, replicas, rcPodLabels, "nginx", "nginx")
@ -276,12 +301,9 @@ func testRollingUpdateDeploymentEvents(f *Framework) {
Expect(newRC).NotTo(Equal(nil))
Expect(events.Items[0].Message).Should(Equal(fmt.Sprintf("Scaled up rc %s to 1", newRC.Name)))
Expect(events.Items[1].Message).Should(Equal(fmt.Sprintf("Scaled down rc %s to 0", rcName)))
// The new RC of this deployment should be revision 4
Expect(newRC.Annotations).NotTo(Equal(nil))
Expect(newRC.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("4"))
// This deployment should be revision 4
Expect(deployment.Annotations).NotTo(Equal(nil))
Expect(deployment.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("4"))
// Check if it's updated to revision 3546343826724305833 correctly
checkDeploymentRevision(c, ns, deploymentName, "3546343826724305833", "redis", "redis")
}
func testRecreateDeployment(f *Framework) {
@ -348,12 +370,9 @@ func testRecreateDeployment(f *Framework) {
Expect(newRC).NotTo(Equal(nil))
Expect(events.Items[0].Message).Should(Equal(fmt.Sprintf("Scaled down rc %s to 0", rcName)))
Expect(events.Items[1].Message).Should(Equal(fmt.Sprintf("Scaled up rc %s to 3", newRC.Name)))
// The new RC of this deployment should be revision 1
Expect(newRC.Annotations).NotTo(Equal(nil))
Expect(newRC.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("1"))
// This deployment should be revision 1
Expect(deployment.Annotations).NotTo(Equal(nil))
Expect(deployment.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("1"))
// Check if it's updated to revision 1 correctly
checkDeploymentRevision(c, ns, deploymentName, "1", "redis", "redis")
}
// testDeploymentCleanUpPolicy tests that deployment supports cleanup policy
@ -457,17 +476,8 @@ func testRolloverDeployment(f *Framework) {
Expect(err).NotTo(HaveOccurred())
// Make sure the deployment starts to scale up and down RCs
waitForPartialEvents(c, ns, deployment, 2)
newRC, err := deploymentutil.GetNewRC(*deployment, c)
Expect(err).NotTo(HaveOccurred())
Expect(newRC).NotTo(Equal(nil))
// The new RC of this deployment should be revision 1
Expect(newRC.Annotations).NotTo(Equal(nil))
Expect(newRC.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("1"))
// This deployment should be revision 1
deployment, err = c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
Expect(deployment.Annotations).NotTo(Equal(nil))
Expect(deployment.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("1"))
// Check if it's updated to revision 1 correctly
_, newRC := checkDeploymentRevision(c, ns, deploymentName, "1", deploymentImageName, deploymentImage)
// Before the deployment finishes, update the deployment to rollover the above 2 rcs and bring up redis pods.
// If the deployment already finished here, the test would fail. When this happens, increase its minReadySeconds or replicas to prevent it.
@ -482,19 +492,8 @@ func testRolloverDeployment(f *Framework) {
err = waitForDeploymentStatus(c, ns, deploymentName, deploymentReplicas, deploymentReplicas-1, deploymentReplicas+1, deploymentMinReadySeconds)
Expect(err).NotTo(HaveOccurred())
// Make sure updated deployment contains "redis" image
deployment, err = c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
Expect(deployment.Spec.Template.Spec.Containers[0].Image).Should(Equal(updatedDeploymentImage))
// Make sure new RC contains "redis" image
newRC, err = deploymentutil.GetNewRC(*deployment, c)
Expect(newRC.Spec.Template.Spec.Containers[0].Image).Should(Equal(updatedDeploymentImage))
// The new RC of this deployment should be revision 2
Expect(newRC.Annotations).NotTo(Equal(nil))
Expect(newRC.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("2"))
// This deployment should be revision 2
Expect(deployment.Annotations).NotTo(Equal(nil))
Expect(deployment.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal("2"))
// Check if it's updated to revision 2 correctly
checkDeploymentRevision(c, ns, deploymentName, "2", updatedDeploymentImage, updatedDeploymentImage)
}
func testPausedDeployment(f *Framework) {
@ -567,3 +566,258 @@ func testPausedDeployment(f *Framework) {
Expect(err).NotTo(HaveOccurred())
}
}
// testRollbackDeployment tests that a deployment is created (revision 1) and updated (revision 2), and
// then rollback to revision 1 (should update template to revision 1, and then update revision 1 to 3),
// and then rollback to last revision.
func testRollbackDeployment(f *Framework) {
ns := f.Namespace.Name
c := f.Client
podName := "nginx"
deploymentPodLabels := map[string]string{"name": podName}
// Create a deployment to create nginx pods.
deploymentName, deploymentImageName := "nginx-deployment", "nginx"
deploymentReplicas := 1
deploymentImage := "nginx"
deploymentStrategyType := extensions.RollingUpdateDeploymentStrategyType
Logf("Creating deployment %s", deploymentName)
d := newDeployment(deploymentName, deploymentReplicas, deploymentPodLabels, deploymentImageName, deploymentImage, deploymentStrategyType, nil)
_, err := c.Deployments(ns).Create(d)
Expect(err).NotTo(HaveOccurred())
defer func() {
deployment, err := c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
Logf("deleting deployment %s", deploymentName)
Expect(c.Deployments(ns).Delete(deploymentName, nil)).NotTo(HaveOccurred())
// TODO: remove this once we can delete rcs with deployment
newRC, err := deploymentutil.GetNewRC(*deployment, c)
Expect(err).NotTo(HaveOccurred())
Expect(c.ReplicationControllers(ns).Delete(newRC.Name)).NotTo(HaveOccurred())
oldRCs, _, err := deploymentutil.GetOldRCs(*deployment, c)
Expect(err).NotTo(HaveOccurred())
for _, oldRC := range oldRCs {
Expect(c.ReplicationControllers(ns).Delete(oldRC.Name)).NotTo(HaveOccurred())
}
}()
// Check that deployment is created fine.
deployment, err := c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
// Verify that the required pods have come up.
err = verifyPods(c, ns, "nginx", false, deploymentReplicas)
if err != nil {
Logf("error in waiting for pods to come up: %s", err)
Expect(err).NotTo(HaveOccurred())
}
deployment, err = c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
// DeploymentStatus should be appropriately updated.
Expect(deployment.Status.Replicas).Should(Equal(deploymentReplicas))
Expect(deployment.Status.UpdatedReplicas).Should(Equal(deploymentReplicas))
// Check if it's updated to revision 1 correctly
checkDeploymentRevision(c, ns, deploymentName, "1", deploymentImageName, deploymentImage)
// Update the deployment to create redis pods.
updatedDeploymentImage := "redis"
updatedDeploymentImageName := "redis"
d.Spec.Template.Spec.Containers[0].Name = updatedDeploymentImageName
d.Spec.Template.Spec.Containers[0].Image = updatedDeploymentImage
Logf("updating deployment %s", deploymentName)
_, err = c.Deployments(ns).Update(d)
Expect(err).NotTo(HaveOccurred())
err = waitForDeploymentStatus(c, ns, deploymentName, deploymentReplicas, deploymentReplicas-1, deploymentReplicas+1, 0)
Expect(err).NotTo(HaveOccurred())
// Check if it's updated to revision 2 correctly
checkDeploymentRevision(c, ns, deploymentName, "2", updatedDeploymentImageName, updatedDeploymentImage)
// Update the deploymentRollback to rollback to revision 1
revision := int64(1)
Logf("rolling back deployment %s to revision %d", deploymentName, revision)
rollback := newDeploymentRollback(deploymentName, nil, revision)
err = c.Deployments(ns).Rollback(rollback)
Expect(err).NotTo(HaveOccurred())
err = waitForDeploymentStatus(c, ns, deploymentName, deploymentReplicas, deploymentReplicas-1, deploymentReplicas+1, 0)
Expect(err).NotTo(HaveOccurred())
// Check if it's updated to revision 3 correctly
checkDeploymentRevision(c, ns, deploymentName, "3", deploymentImageName, deploymentImage)
// Update the deploymentRollback to rollback to last revision
revision = 0
Logf("rolling back deployment %s to last revision", deploymentName)
rollback = newDeploymentRollback(deploymentName, nil, revision)
err = c.Deployments(ns).Rollback(rollback)
Expect(err).NotTo(HaveOccurred())
err = waitForDeploymentStatus(c, ns, deploymentName, deploymentReplicas, deploymentReplicas-1, deploymentReplicas+1, 0)
Expect(err).NotTo(HaveOccurred())
// Check if it's updated to revision 4 correctly
checkDeploymentRevision(c, ns, deploymentName, "4", updatedDeploymentImageName, updatedDeploymentImage)
}
// testRollbackDeploymentRCNoRevision tests that deployment supports rollback even when there's old RC without revision.
// An old RC without revision is created, and then a deployment is created (v1). The deployment shouldn't add revision
// annotation to the old RC. Then rollback the deployment to last revision, and it should fail and emit related event.
// Then update the deployment to v2 and rollback it to v1 should succeed and emit related event, now the deployment
// becomes v3. Then rollback the deployment to v10 (doesn't exist in history) should fail and emit related event.
// Finally, rollback the deployment (v3) to v3 should be no-op and emit related event.
func testRollbackDeploymentRCNoRevision(f *Framework) {
ns := f.Namespace.Name
c := f.Client
podName := "nginx"
deploymentPodLabels := map[string]string{"name": podName}
rcPodLabels := map[string]string{
"name": podName,
"pod": "nginx",
}
rcName := "nginx-controller"
rcReplicas := 0
rc := newRC(rcName, rcReplicas, rcPodLabels, "nginx", "nginx")
rc.Annotations = make(map[string]string)
rc.Annotations["make"] = "difference"
_, err := c.ReplicationControllers(ns).Create(rc)
Expect(err).NotTo(HaveOccurred())
defer func() {
Logf("deleting replication controller %s", rcName)
Expect(c.ReplicationControllers(ns).Delete(rcName)).NotTo(HaveOccurred())
}()
// Create a deployment to create nginx pods, which have different template than the rc created above.
deploymentName, deploymentImageName := "nginx-deployment", "nginx"
deploymentReplicas := 1
deploymentImage := "nginx"
deploymentStrategyType := extensions.RollingUpdateDeploymentStrategyType
Logf("Creating deployment %s", deploymentName)
d := newDeployment(deploymentName, deploymentReplicas, deploymentPodLabels, deploymentImageName, deploymentImage, deploymentStrategyType, nil)
_, err = c.Deployments(ns).Create(d)
Expect(err).NotTo(HaveOccurred())
defer func() {
deployment, err := c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
Logf("deleting deployment %s", deploymentName)
Expect(c.Deployments(ns).Delete(deploymentName, nil)).NotTo(HaveOccurred())
// TODO: remove this once we can delete rcs with deployment
newRC, err := deploymentutil.GetNewRC(*deployment, c)
Expect(err).NotTo(HaveOccurred())
Expect(c.ReplicationControllers(ns).Delete(newRC.Name)).NotTo(HaveOccurred())
oldRCs, _, err := deploymentutil.GetOldRCs(*deployment, c)
Expect(err).NotTo(HaveOccurred())
for _, oldRC := range oldRCs {
Expect(c.ReplicationControllers(ns).Delete(oldRC.Name)).NotTo(HaveOccurred())
}
}()
// Check that deployment is created fine.
deployment, err := c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
// Verify that the required pods have come up.
err = verifyPods(c, ns, "nginx", false, deploymentReplicas)
if err != nil {
Logf("error in waiting for pods to come up: %s", err)
Expect(err).NotTo(HaveOccurred())
}
deployment, err = c.Deployments(ns).Get(deploymentName)
Expect(err).NotTo(HaveOccurred())
// DeploymentStatus should be appropriately updated.
Expect(deployment.Status.Replicas).Should(Equal(deploymentReplicas))
Expect(deployment.Status.UpdatedReplicas).Should(Equal(deploymentReplicas))
// Check if it's updated to revision 1 correctly
checkDeploymentRevision(c, ns, deploymentName, "1", deploymentImageName, deploymentImage)
// Check that the rc we created still doesn't contain revision information
rc, err = c.ReplicationControllers(ns).Get(rcName)
Expect(rc.Annotations[deploymentutil.RevisionAnnotation]).Should(Equal(""))
// Update the deploymentRollback to rollback to last revision
// Since there's only 1 revision in history, it should stay as revision 1
revision := int64(0)
Logf("rolling back deployment %s to last revision", deploymentName)
rollback := newDeploymentRollback(deploymentName, nil, revision)
err = c.Deployments(ns).Rollback(rollback)
Expect(err).NotTo(HaveOccurred())
// There should be revision not found event since there's no last revision
waitForEvents(c, ns, deployment, 2)
events, err := c.Events(ns).Search(deployment)
Expect(err).NotTo(HaveOccurred())
Expect(events.Items[1].Reason).Should(Equal("DeploymentRollbackRevisionNotFound"))
// Check if it's still revision 1
checkDeploymentRevision(c, ns, deploymentName, "1", deploymentImageName, deploymentImage)
// Update the deployment to create redis pods.
updatedDeploymentImage := "redis"
updatedDeploymentImageName := "redis"
d.Spec.Template.Spec.Containers[0].Name = updatedDeploymentImageName
d.Spec.Template.Spec.Containers[0].Image = updatedDeploymentImage
Logf("updating deployment %s", deploymentName)
_, err = c.Deployments(ns).Update(d)
Expect(err).NotTo(HaveOccurred())
err = waitForDeploymentStatus(c, ns, deploymentName, deploymentReplicas, deploymentReplicas-1, deploymentReplicas+1, 0)
Expect(err).NotTo(HaveOccurred())
// Check if it's updated to revision 2 correctly
checkDeploymentRevision(c, ns, deploymentName, "2", updatedDeploymentImageName, updatedDeploymentImage)
// Update the deploymentRollback to rollback to revision 1
revision = 1
Logf("rolling back deployment %s to revision %d", deploymentName, revision)
rollback = newDeploymentRollback(deploymentName, nil, revision)
err = c.Deployments(ns).Rollback(rollback)
Expect(err).NotTo(HaveOccurred())
err = waitForDeploymentStatus(c, ns, deploymentName, deploymentReplicas, deploymentReplicas-1, deploymentReplicas+1, 0)
Expect(err).NotTo(HaveOccurred())
// There should be rollback event after we rollback to revision 1
waitForEvents(c, ns, deployment, 5)
events, err = c.Events(ns).Search(deployment)
Expect(err).NotTo(HaveOccurred())
Expect(events.Items[4].Reason).Should(Equal("DeploymentRollback"))
// Check if it's updated to revision 3 correctly
checkDeploymentRevision(c, ns, deploymentName, "3", deploymentImageName, deploymentImage)
// Update the deploymentRollback to rollback to revision 10
// Since there's no revision 10 in history, it should stay as revision 3, and emit an event
revision = 10
Logf("rolling back deployment %s to revision %d", deploymentName, revision)
rollback = newDeploymentRollback(deploymentName, nil, revision)
err = c.Deployments(ns).Rollback(rollback)
Expect(err).NotTo(HaveOccurred())
// There should be revision not found event since there's no revision 10
waitForEvents(c, ns, deployment, 7)
events, err = c.Events(ns).Search(deployment)
Expect(err).NotTo(HaveOccurred())
Expect(events.Items[6].Reason).Should(Equal("DeploymentRollbackRevisionNotFound"))
// Check if it's still revision 3
checkDeploymentRevision(c, ns, deploymentName, "3", deploymentImageName, deploymentImage)
// Update the deploymentRollback to rollback to revision 3
// Since it's already revision 3, it should be no-op and emit an event
revision = 3
Logf("rolling back deployment %s to revision %d", deploymentName, revision)
rollback = newDeploymentRollback(deploymentName, nil, revision)
err = c.Deployments(ns).Rollback(rollback)
Expect(err).NotTo(HaveOccurred())
// There should be revision template unchanged event since it's already revision 3
waitForEvents(c, ns, deployment, 8)
events, err = c.Events(ns).Search(deployment)
Expect(err).NotTo(HaveOccurred())
Expect(events.Items[7].Reason).Should(Equal("DeploymentRollbackTemplateUnchanged"))
// Check if it's still revision 3
checkDeploymentRevision(c, ns, deploymentName, "3", deploymentImageName, deploymentImage)
}