Commit 3396db95 authored by Janet Kuo's avatar Janet Kuo

Add rollback subresource; add rollback logic to deployment controller

parent 470b69b5
......@@ -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 }
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -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 (
......
......@@ -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
}
......
......@@ -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,
......
......@@ -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,
......
......@@ -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 }
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -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 (
......
......@@ -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.",
......
......@@ -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)
}
......
......@@ -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"},
......
......@@ -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()
}
......@@ -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)
}
......@@ -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
}
......@@ -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)
......
......@@ -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
}
......
......@@ -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)
}
}
......@@ -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
......
......@@ -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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment