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 deployment 18 19import ( 20 "context" 21 "fmt" 22 "strconv" 23 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/klog/v2" 26 27 apps "k8s.io/api/apps/v1" 28 "k8s.io/api/core/v1" 29 extensions "k8s.io/api/extensions/v1beta1" 30 deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" 31) 32 33// rollback the deployment to the specified revision. In any case cleanup the rollback spec. 34func (dc *DeploymentController) rollback(d *apps.Deployment, rsList []*apps.ReplicaSet) error { 35 newRS, allOldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, true) 36 if err != nil { 37 return err 38 } 39 40 allRSs := append(allOldRSs, newRS) 41 rollbackTo := getRollbackTo(d) 42 // If rollback revision is 0, rollback to the last revision 43 if rollbackTo.Revision == 0 { 44 if rollbackTo.Revision = deploymentutil.LastRevision(allRSs); rollbackTo.Revision == 0 { 45 // If we still can't find the last revision, gives up rollback 46 dc.emitRollbackWarningEvent(d, deploymentutil.RollbackRevisionNotFound, "Unable to find last revision.") 47 // Gives up rollback 48 return dc.updateDeploymentAndClearRollbackTo(d) 49 } 50 } 51 for _, rs := range allRSs { 52 v, err := deploymentutil.Revision(rs) 53 if err != nil { 54 klog.V(4).Infof("Unable to extract revision from deployment's replica set %q: %v", rs.Name, err) 55 continue 56 } 57 if v == rollbackTo.Revision { 58 klog.V(4).Infof("Found replica set %q with desired revision %d", rs.Name, v) 59 // rollback by copying podTemplate.Spec from the replica set 60 // revision number will be incremented during the next getAllReplicaSetsAndSyncRevision call 61 // no-op if the spec matches current deployment's podTemplate.Spec 62 performedRollback, err := dc.rollbackToTemplate(d, rs) 63 if performedRollback && err == nil { 64 dc.emitRollbackNormalEvent(d, fmt.Sprintf("Rolled back deployment %q to revision %d", d.Name, rollbackTo.Revision)) 65 } 66 return err 67 } 68 } 69 dc.emitRollbackWarningEvent(d, deploymentutil.RollbackRevisionNotFound, "Unable to find the revision to rollback to.") 70 // Gives up rollback 71 return dc.updateDeploymentAndClearRollbackTo(d) 72} 73 74// rollbackToTemplate compares the templates of the provided deployment and replica set and 75// updates the deployment with the replica set template in case they are different. It also 76// cleans up the rollback spec so subsequent requeues of the deployment won't end up in here. 77func (dc *DeploymentController) rollbackToTemplate(d *apps.Deployment, rs *apps.ReplicaSet) (bool, error) { 78 performedRollback := false 79 if !deploymentutil.EqualIgnoreHash(&d.Spec.Template, &rs.Spec.Template) { 80 klog.V(4).Infof("Rolling back deployment %q to template spec %+v", d.Name, rs.Spec.Template.Spec) 81 deploymentutil.SetFromReplicaSetTemplate(d, rs.Spec.Template) 82 // set RS (the old RS we'll rolling back to) annotations back to the deployment; 83 // otherwise, the deployment's current annotations (should be the same as current new RS) will be copied to the RS after the rollback. 84 // 85 // For example, 86 // A Deployment has old RS1 with annotation {change-cause:create}, and new RS2 {change-cause:edit}. 87 // Note that both annotations are copied from Deployment, and the Deployment should be annotated {change-cause:edit} as well. 88 // Now, rollback Deployment to RS1, we should update Deployment's pod-template and also copy annotation from RS1. 89 // Deployment is now annotated {change-cause:create}, and we have new RS1 {change-cause:create}, old RS2 {change-cause:edit}. 90 // 91 // If we don't copy the annotations back from RS to deployment on rollback, the Deployment will stay as {change-cause:edit}, 92 // and new RS1 becomes {change-cause:edit} (copied from deployment after rollback), old RS2 {change-cause:edit}, which is not correct. 93 deploymentutil.SetDeploymentAnnotationsTo(d, rs) 94 performedRollback = true 95 } else { 96 klog.V(4).Infof("Rolling back to a revision that contains the same template as current deployment %q, skipping rollback...", d.Name) 97 eventMsg := fmt.Sprintf("The rollback revision contains the same template as current deployment %q", d.Name) 98 dc.emitRollbackWarningEvent(d, deploymentutil.RollbackTemplateUnchanged, eventMsg) 99 } 100 101 return performedRollback, dc.updateDeploymentAndClearRollbackTo(d) 102} 103 104func (dc *DeploymentController) emitRollbackWarningEvent(d *apps.Deployment, reason, message string) { 105 dc.eventRecorder.Eventf(d, v1.EventTypeWarning, reason, message) 106} 107 108func (dc *DeploymentController) emitRollbackNormalEvent(d *apps.Deployment, message string) { 109 dc.eventRecorder.Eventf(d, v1.EventTypeNormal, deploymentutil.RollbackDone, message) 110} 111 112// updateDeploymentAndClearRollbackTo sets .spec.rollbackTo to nil and update the input deployment 113// It is assumed that the caller will have updated the deployment template appropriately (in case 114// we want to rollback). 115func (dc *DeploymentController) updateDeploymentAndClearRollbackTo(d *apps.Deployment) error { 116 klog.V(4).Infof("Cleans up rollbackTo of deployment %q", d.Name) 117 setRollbackTo(d, nil) 118 _, err := dc.client.AppsV1().Deployments(d.Namespace).Update(context.TODO(), d, metav1.UpdateOptions{}) 119 return err 120} 121 122// TODO: Remove this when extensions/v1beta1 and apps/v1beta1 Deployment are dropped. 123func getRollbackTo(d *apps.Deployment) *extensions.RollbackConfig { 124 // Extract the annotation used for round-tripping the deprecated RollbackTo field. 125 revision := d.Annotations[apps.DeprecatedRollbackTo] 126 if revision == "" { 127 return nil 128 } 129 revision64, err := strconv.ParseInt(revision, 10, 64) 130 if err != nil { 131 // If it's invalid, ignore it. 132 return nil 133 } 134 return &extensions.RollbackConfig{ 135 Revision: revision64, 136 } 137} 138 139// TODO: Remove this when extensions/v1beta1 and apps/v1beta1 Deployment are dropped. 140func setRollbackTo(d *apps.Deployment, rollbackTo *extensions.RollbackConfig) { 141 if rollbackTo == nil { 142 delete(d.Annotations, apps.DeprecatedRollbackTo) 143 return 144 } 145 if d.Annotations == nil { 146 d.Annotations = make(map[string]string) 147 } 148 d.Annotations[apps.DeprecatedRollbackTo] = strconv.FormatInt(rollbackTo.Revision, 10) 149} 150