2014-12-10 21:48:48 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2014-12-10 21:48:48 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package cmd
import (
2015-04-23 23:27:19 +00:00
"bytes"
2014-12-10 21:48:48 +00:00
"fmt"
"io"
2015-03-27 23:24:59 +00:00
"os"
2015-08-05 14:21:47 +00:00
"time"
2014-12-10 21:48:48 +00:00
2015-05-13 13:16:24 +00:00
"github.com/golang/glog"
2015-08-05 22:05:17 +00:00
"github.com/spf13/cobra"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
2015-09-01 13:35:27 +00:00
"k8s.io/kubernetes/pkg/api/meta"
2015-11-13 13:13:55 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/kubectl"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
2015-11-10 06:28:45 +00:00
"k8s.io/kubernetes/pkg/util/intstr"
2014-12-10 21:48:48 +00:00
)
2015-08-14 18:46:43 +00:00
// RollingUpdateOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
// referencing the cmd.Flags()
type RollingUpdateOptions struct {
Filenames [ ] string
}
2014-12-10 21:48:48 +00:00
const (
2015-03-27 23:24:59 +00:00
rollingUpdate_long = ` Perform a rolling update of the given ReplicationController .
2014-12-10 21:48:48 +00:00
2015-06-16 21:48:51 +00:00
Replaces the specified replication controller with a new replication controller by updating one pod at a time to use the
2014-12-10 21:48:48 +00:00
new PodTemplate . The new - controller . json must specify the same namespace as the
2015-06-16 21:48:51 +00:00
existing replication controller and overwrite at least one ( common ) label in its replicaSelector . `
2015-08-12 16:50:09 +00:00
rollingUpdate_example = ` # Update pods of frontend - v1 using new replication controller data in frontend - v2 . json .
2015-03-27 23:24:59 +00:00
$ kubectl rolling - update frontend - v1 - f frontend - v2 . json
2014-12-10 21:48:48 +00:00
2015-08-12 16:50:09 +00:00
# Update pods of frontend - v1 using JSON data passed into stdin .
2015-04-23 23:27:19 +00:00
$ cat frontend - v2 . json | kubectl rolling - update frontend - v1 - f -
2015-08-12 16:50:09 +00:00
# Update the pods of frontend - v1 to frontend - v2 by just changing the image , and switching the
# name of the replication controller .
2015-04-23 23:27:19 +00:00
$ kubectl rolling - update frontend - v1 frontend - v2 -- image = image : v2
2015-04-23 23:27:19 +00:00
2015-11-11 21:29:54 +00:00
# Update the pods of frontend by just changing the image , and keeping the old name .
2015-04-23 23:27:19 +00:00
$ kubectl rolling - update frontend -- image = image : v2
2015-11-11 21:29:54 +00:00
# Abort and reverse an existing rollout in progress ( from frontend - v1 to frontend - v2 ) .
$ kubectl rolling - update frontend - v1 frontend - v2 -- rollback
2015-04-23 23:27:19 +00:00
`
2015-02-20 21:28:43 +00:00
)
2015-02-03 17:59:21 +00:00
2015-08-05 14:21:47 +00:00
var (
updatePeriod , _ = time . ParseDuration ( "1m0s" )
timeout , _ = time . ParseDuration ( "5m0s" )
pollInterval , _ = time . ParseDuration ( "3s" )
)
2015-04-07 18:21:25 +00:00
func NewCmdRollingUpdate ( f * cmdutil . Factory , out io . Writer ) * cobra . Command {
2015-08-14 18:46:43 +00:00
options := & RollingUpdateOptions { }
2015-02-20 21:28:43 +00:00
cmd := & cobra . Command {
2015-04-23 23:27:19 +00:00
Use : "rolling-update OLD_CONTROLLER_NAME ([NEW_CONTROLLER_NAME] --image=NEW_CONTAINER_IMAGE | -f NEW_CONTROLLER_SPEC)" ,
2015-03-27 23:24:59 +00:00
// rollingupdate is deprecated.
Aliases : [ ] string { "rollingupdate" } ,
2015-02-20 21:28:43 +00:00
Short : "Perform a rolling update of the given ReplicationController." ,
2015-03-27 23:24:59 +00:00
Long : rollingUpdate_long ,
Example : rollingUpdate_example ,
2014-12-10 21:48:48 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
2015-08-14 18:46:43 +00:00
err := RunRollingUpdate ( f , out , cmd , args , options )
2015-04-07 18:21:25 +00:00
cmdutil . CheckErr ( err )
2015-03-09 22:08:16 +00:00
} ,
}
2015-08-05 14:21:47 +00:00
cmd . Flags ( ) . Duration ( "update-period" , updatePeriod , ` Time to wait between updating pods. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". ` )
cmd . Flags ( ) . Duration ( "poll-interval" , pollInterval , ` Time delay between polling for replication controller status after the update. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". ` )
cmd . Flags ( ) . Duration ( "timeout" , timeout , ` Max time to wait for a replication controller to update before giving up. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". ` )
2015-03-29 03:39:19 +00:00
usage := "Filename or URL to file to use to create the new replication controller."
2015-08-14 18:46:43 +00:00
kubectl . AddJsonFilenameFlag ( cmd , & options . Filenames , usage )
2015-03-29 03:39:19 +00:00
cmd . MarkFlagRequired ( "filename" )
2015-10-19 23:59:02 +00:00
cmd . Flags ( ) . String ( "image" , "" , "Image to use for upgrading the replication controller. Must be distinct from the existing image (either new image or new image tag). Can not be used with --filename/-f" )
2015-03-29 03:39:19 +00:00
cmd . MarkFlagRequired ( "image" )
2015-04-23 23:27:19 +00:00
cmd . Flags ( ) . String ( "deployment-label-key" , "deployment" , "The key to use to differentiate between two different controllers, default 'deployment'. Only relevant when --image is specified, ignored otherwise" )
cmd . Flags ( ) . Bool ( "dry-run" , false , "If true, print out the changes that would be made, but don't actually make them." )
2015-04-30 17:28:36 +00:00
cmd . Flags ( ) . Bool ( "rollback" , false , "If true, this is a request to abort an existing rollout that is partially rolled out. It effectively reverses current and next and runs a rollout" )
2015-09-10 21:58:09 +00:00
cmdutil . AddValidateFlags ( cmd )
2015-04-23 23:27:19 +00:00
cmdutil . AddPrinterFlags ( cmd )
2015-03-09 22:08:16 +00:00
return cmd
}
2015-01-02 18:08:37 +00:00
2015-09-25 02:55:03 +00:00
func validateArguments ( cmd * cobra . Command , filenames , args [ ] string ) error {
deploymentKey := cmdutil . GetFlagString ( cmd , "deployment-label-key" )
image := cmdutil . GetFlagString ( cmd , "image" )
rollback := cmdutil . GetFlagBool ( cmd , "rollback" )
2015-04-23 23:27:19 +00:00
if len ( deploymentKey ) == 0 {
2015-09-25 02:55:03 +00:00
return cmdutil . UsageError ( cmd , "--deployment-label-key can not be empty" )
2015-04-23 23:27:19 +00:00
}
2015-03-29 03:39:19 +00:00
if len ( filenames ) > 1 {
2015-09-25 02:55:03 +00:00
return cmdutil . UsageError ( cmd , "May only specify a single filename for new controller" )
2015-03-29 03:39:19 +00:00
}
2015-09-25 02:55:03 +00:00
if ! rollback {
if len ( filenames ) == 0 && len ( image ) == 0 {
return cmdutil . UsageError ( cmd , "Must specify --filename or --image for new controller" )
}
if len ( filenames ) != 0 && len ( image ) != 0 {
return cmdutil . UsageError ( cmd , "--filename and --image can not both be specified" )
}
} else {
if len ( filenames ) != 0 || len ( image ) != 0 {
return cmdutil . UsageError ( cmd , "Don't specify --filename or --image on rollback" )
}
2015-04-23 23:27:19 +00:00
}
2015-09-25 02:55:03 +00:00
2015-04-23 23:27:19 +00:00
if len ( args ) < 1 {
2015-09-25 02:55:03 +00:00
return cmdutil . UsageError ( cmd , "Must specify the controller to update" )
2015-04-23 23:27:19 +00:00
}
2015-09-25 02:55:03 +00:00
return nil
2015-04-23 23:27:19 +00:00
}
2015-08-14 18:46:43 +00:00
func RunRollingUpdate ( f * cmdutil . Factory , out io . Writer , cmd * cobra . Command , args [ ] string , options * RollingUpdateOptions ) error {
2015-07-09 23:15:42 +00:00
if len ( os . Args ) > 1 && os . Args [ 1 ] == "rollingupdate" {
2015-03-27 23:24:59 +00:00
printDeprecationWarning ( "rolling-update" , "rollingupdate" )
}
2015-09-25 02:55:03 +00:00
err := validateArguments ( cmd , options . Filenames , args )
2015-04-23 23:27:19 +00:00
if err != nil {
return err
2015-03-09 22:08:16 +00:00
}
2015-09-25 02:55:03 +00:00
deploymentKey := cmdutil . GetFlagString ( cmd , "deployment-label-key" )
filename := ""
image := cmdutil . GetFlagString ( cmd , "image" )
oldName := args [ 0 ]
rollback := cmdutil . GetFlagBool ( cmd , "rollback" )
2015-04-07 18:21:25 +00:00
period := cmdutil . GetFlagDuration ( cmd , "update-period" )
interval := cmdutil . GetFlagDuration ( cmd , "poll-interval" )
timeout := cmdutil . GetFlagDuration ( cmd , "timeout" )
2015-04-23 23:27:19 +00:00
dryrun := cmdutil . GetFlagBool ( cmd , "dry-run" )
2015-09-01 13:35:27 +00:00
outputFormat := cmdutil . GetFlagString ( cmd , "output" )
2014-12-10 21:48:48 +00:00
2015-09-25 02:55:03 +00:00
if len ( options . Filenames ) > 0 {
filename = options . Filenames [ 0 ]
}
2015-06-26 20:49:34 +00:00
cmdNamespace , enforceNamespace , err := f . DefaultNamespace ( )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-01-08 13:40:02 +00:00
2015-04-23 23:27:19 +00:00
client , err := f . Client ( )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-04-23 23:27:19 +00:00
2015-05-01 07:31:01 +00:00
var newRc * api . ReplicationController
2015-04-23 23:27:19 +00:00
// fetch rc
oldRc , err := client . ReplicationControllers ( cmdNamespace ) . Get ( oldName )
if err != nil {
2015-05-01 07:31:01 +00:00
if ! errors . IsNotFound ( err ) || len ( image ) == 0 || len ( args ) > 1 {
return err
}
// We're in the middle of a rename, look for an RC with a source annotation of oldName
2015-07-28 18:43:48 +00:00
newRc , err := kubectl . FindSourceController ( client , cmdNamespace , oldName )
2015-05-01 07:31:01 +00:00
if err != nil {
return err
}
2015-07-28 18:43:48 +00:00
return kubectl . Rename ( client , newRc , oldName )
2015-04-23 23:27:19 +00:00
}
2015-05-01 07:31:01 +00:00
var keepOldName bool
2015-06-23 01:56:53 +00:00
var replicasDefaulted bool
2015-04-23 23:27:19 +00:00
2015-04-23 23:27:19 +00:00
mapper , typer := f . Object ( )
if len ( filename ) != 0 {
2015-09-11 03:54:22 +00:00
schema , err := f . Validator ( cmdutil . GetFlagBool ( cmd , "validate" ) , cmdutil . GetFlagString ( cmd , "schema-cache-dir" ) )
2015-05-07 20:53:43 +00:00
if err != nil {
return err
}
2015-06-26 20:49:34 +00:00
2015-06-23 01:56:53 +00:00
request := resource . NewBuilder ( mapper , typer , f . ClientMapperForCommand ( ) ) .
2015-05-07 20:53:43 +00:00
Schema ( schema ) .
2015-06-26 20:49:34 +00:00
NamespaceParam ( cmdNamespace ) . DefaultNamespace ( ) .
FilenameParam ( enforceNamespace , filename ) .
2015-06-23 01:56:53 +00:00
Do ( )
obj , err := request . Object ( )
2015-04-23 23:27:19 +00:00
if err != nil {
return err
}
var ok bool
2015-05-22 16:44:00 +00:00
// Handle filename input from stdin. The resource builder always returns an api.List
// when creating resource(s) from a stream.
if list , ok := obj . ( * api . List ) ; ok {
if len ( list . Items ) > 1 {
return cmdutil . UsageError ( cmd , "%s specifies multiple items" , filename )
}
obj = list . Items [ 0 ]
}
2015-04-23 23:27:19 +00:00
newRc , ok = obj . ( * api . ReplicationController )
if ! ok {
2015-05-13 13:16:24 +00:00
if _ , kind , err := typer . ObjectVersionAndKind ( obj ) ; err == nil {
return cmdutil . UsageError ( cmd , "%s contains a %s not a ReplicationController" , filename , kind )
}
glog . V ( 4 ) . Infof ( "Object %#v is not a ReplicationController" , obj )
2015-04-23 23:27:19 +00:00
return cmdutil . UsageError ( cmd , "%s does not specify a valid ReplicationController" , filename )
}
2015-06-23 01:56:53 +00:00
infos , err := request . Infos ( )
if err != nil || len ( infos ) != 1 {
glog . V ( 2 ) . Infof ( "was not able to recover adequate information to discover if .spec.replicas was defaulted" )
} else {
replicasDefaulted = isReplicasDefaulted ( infos [ 0 ] )
}
2015-04-23 23:27:19 +00:00
}
2015-04-29 22:06:42 +00:00
// If the --image option is specified, we need to create a new rc with at least one different selector
// than the old rc. This selector is the hash of the rc, which will differ because the new rc has a
// different image.
2015-04-23 23:27:19 +00:00
if len ( image ) != 0 {
2015-05-01 07:31:01 +00:00
keepOldName = len ( args ) == 1
newName := findNewName ( args , oldRc )
if newRc , err = kubectl . LoadExistingNextReplicationController ( client , cmdNamespace , newName ) ; err != nil {
return err
2015-04-28 00:10:39 +00:00
}
2015-05-01 07:31:01 +00:00
if newRc != nil {
2015-10-19 23:59:02 +00:00
if inProgressImage := newRc . Spec . Template . Spec . Containers [ 0 ] . Image ; inProgressImage != image {
return cmdutil . UsageError ( cmd , "Found existing in-progress update to image (%s).\nEither continue in-progress update with --image=%s or rollback with --rollback" , inProgressImage , inProgressImage )
}
2015-05-01 07:31:01 +00:00
fmt . Fprintf ( out , "Found existing update in progress (%s), resuming.\n" , newRc . Name )
} else {
2015-10-19 23:59:02 +00:00
if oldRc . Spec . Template . Spec . Containers [ 0 ] . Image == image {
return cmdutil . UsageError ( cmd , "Specified --image must be distinct from existing container image" )
}
2015-05-01 07:31:01 +00:00
newRc , err = kubectl . CreateNewControllerFromCurrentController ( client , cmdNamespace , oldName , newName , image , deploymentKey )
2015-04-28 00:10:39 +00:00
if err != nil {
2015-04-23 23:27:19 +00:00
return err
}
2015-05-01 07:31:01 +00:00
}
// Update the existing replication controller with pointers to the 'next' controller
// and adding the <deploymentKey> label if necessary to distinguish it from the 'next' controller.
oldHash , err := api . HashObject ( oldRc , client . Codec )
if err != nil {
return err
}
oldRc , err = kubectl . UpdateExistingReplicationController ( client , oldRc , cmdNamespace , newRc . Name , deploymentKey , oldHash , out )
if err != nil {
return err
2015-04-23 23:27:19 +00:00
}
2015-03-09 22:08:16 +00:00
}
2015-09-25 02:55:03 +00:00
if rollback {
keepOldName = len ( args ) == 1
newName := findNewName ( args , oldRc )
if newRc , err = kubectl . LoadExistingNextReplicationController ( client , cmdNamespace , newName ) ; err != nil {
return err
}
if newRc == nil {
return cmdutil . UsageError ( cmd , "Could not find %s to rollback.\n" , newName )
}
}
2015-05-01 07:31:01 +00:00
if oldName == newRc . Name {
2015-04-07 18:21:25 +00:00
return cmdutil . UsageError ( cmd , "%s cannot have the same name as the existing ReplicationController %s" ,
2015-03-09 22:08:16 +00:00
filename , oldName )
}
2014-12-10 21:48:48 +00:00
2015-07-28 18:43:48 +00:00
updater := kubectl . NewRollingUpdater ( newRc . Namespace , client )
2015-03-09 22:08:16 +00:00
2015-04-29 22:06:42 +00:00
// To successfully pull off a rolling update the new and old rc have to differ
// by at least one selector. Every new pod should have the selector and every
// old pod should not have the selector.
2015-03-09 22:08:16 +00:00
var hasLabel bool
for key , oldValue := range oldRc . Spec . Selector {
if newValue , ok := newRc . Spec . Selector [ key ] ; ok && newValue != oldValue {
hasLabel = true
break
}
}
if ! hasLabel {
2015-04-07 18:21:25 +00:00
return cmdutil . UsageError ( cmd , "%s must specify a matching key with non-equal value in Selector for %s" ,
2015-03-09 22:08:16 +00:00
filename , oldName )
}
2015-05-21 21:10:25 +00:00
// TODO: handle scales during rolling update
2015-06-23 01:56:53 +00:00
if replicasDefaulted {
2015-03-09 22:08:16 +00:00
newRc . Spec . Replicas = oldRc . Spec . Replicas
}
2015-04-23 23:27:19 +00:00
if dryrun {
oldRcData := & bytes . Buffer { }
newRcData := & bytes . Buffer { }
2015-09-01 13:35:27 +00:00
if outputFormat == "" {
oldRcData . WriteString ( oldRc . Name )
newRcData . WriteString ( newRc . Name )
} else {
if err := f . PrintObject ( cmd , oldRc , oldRcData ) ; err != nil {
return err
}
if err := f . PrintObject ( cmd , newRc , newRcData ) ; err != nil {
return err
}
2015-04-23 23:27:19 +00:00
}
fmt . Fprintf ( out , "Rolling from:\n%s\nTo:\n%s\n" , string ( oldRcData . Bytes ( ) ) , string ( newRcData . Bytes ( ) ) )
return nil
}
2015-04-23 23:27:19 +00:00
updateCleanupPolicy := kubectl . DeleteRollingUpdateCleanupPolicy
if keepOldName {
updateCleanupPolicy = kubectl . RenameRollingUpdateCleanupPolicy
}
2015-04-30 17:28:36 +00:00
config := & kubectl . RollingUpdaterConfig {
2015-06-18 20:29:28 +00:00
Out : out ,
OldRc : oldRc ,
NewRc : newRc ,
UpdatePeriod : period ,
Interval : interval ,
Timeout : timeout ,
CleanupPolicy : updateCleanupPolicy ,
2015-11-10 06:28:45 +00:00
MaxUnavailable : intstr . FromInt ( 0 ) ,
MaxSurge : intstr . FromInt ( 1 ) ,
2015-04-30 17:28:36 +00:00
}
2015-09-25 02:55:03 +00:00
if rollback {
2015-09-28 18:52:01 +00:00
err = kubectl . AbortRollingUpdate ( config )
if err != nil {
return err
}
2015-04-30 17:28:36 +00:00
client . ReplicationControllers ( config . NewRc . Namespace ) . Update ( config . NewRc )
}
err = updater . Update ( config )
2015-03-09 22:08:16 +00:00
if err != nil {
return err
}
2015-09-01 13:35:27 +00:00
message := "rolling updated"
2015-04-23 23:27:19 +00:00
if keepOldName {
2015-09-01 13:35:27 +00:00
newRc . Name = oldName
2015-04-23 23:27:19 +00:00
} else {
2015-09-01 13:35:27 +00:00
message = fmt . Sprintf ( "rolling updated to %q" , newRc . Name )
}
newRc , err = client . ReplicationControllers ( cmdNamespace ) . Get ( newRc . Name )
if err != nil {
return err
}
if outputFormat != "" {
return f . PrintObject ( cmd , newRc , out )
}
_ , kind , err := api . Scheme . ObjectVersionAndKind ( newRc )
if err != nil {
return err
2015-04-23 23:27:19 +00:00
}
2015-09-01 13:35:27 +00:00
_ , res := meta . KindToResource ( kind , false )
cmdutil . PrintSuccess ( mapper , false , out , res , oldName , message )
2015-03-09 22:08:16 +00:00
return nil
2014-12-10 21:48:48 +00:00
}
2015-04-23 23:27:19 +00:00
2015-05-01 07:31:01 +00:00
func findNewName ( args [ ] string , oldRc * api . ReplicationController ) string {
if len ( args ) >= 2 {
return args [ 1 ]
2015-04-23 23:27:19 +00:00
}
2015-05-01 07:31:01 +00:00
if oldRc != nil {
newName , _ := kubectl . GetNextControllerAnnotation ( oldRc )
return newName
2015-04-23 23:27:19 +00:00
}
2015-05-01 07:31:01 +00:00
return ""
2015-04-23 23:27:19 +00:00
}
2015-06-23 01:56:53 +00:00
func isReplicasDefaulted ( info * resource . Info ) bool {
if info == nil || info . VersionedObject == nil {
// was unable to recover versioned info
return false
}
2015-11-13 13:13:55 +00:00
switch info . Mapping . GroupVersionKind . GroupVersion ( ) {
case unversioned . GroupVersion { Version : "v1" } :
2015-06-23 01:56:53 +00:00
if rc , ok := info . VersionedObject . ( * v1 . ReplicationController ) ; ok {
return rc . Spec . Replicas == nil
}
}
return false
}