kubectl: Support scaling deployments

This commit adds support for using kubectl scale to scale deployments. Makes use of the
deployments/scale endpoint instead of updating deployment.spec.replicas directly.
This commit is contained in:
Michail Kargakis 2015-11-13 13:44:03 +01:00
parent a7425bf070
commit 99fc35880b
11 changed files with 558 additions and 200 deletions

View File

@ -3,7 +3,7 @@
.SH NAME
.PP
kubectl scale \- Set a new size for a Replication Controller.
kubectl scale \- Set a new size for a Replication Controller, Job, or Deployment.
.SH SYNOPSIS
@ -13,7 +13,7 @@ kubectl scale \- Set a new size for a Replication Controller.
.SH DESCRIPTION
.PP
Set a new size for a Replication Controller.
Set a new size for a Replication Controller, Job, or Deployment.
.PP
Scale also allows users to specify one or more preconditions for the scale action.
@ -25,11 +25,11 @@ scale is sent to the server.
.SH OPTIONS
.PP
\fB\-\-current\-replicas\fP=\-1
Precondition for current size. Requires that the current size of the replication controller match this value in order to scale.
Precondition for current size. Requires that the current size of the resource match this value in order to scale.
.PP
\fB\-f\fP, \fB\-\-filename\fP=[]
Filename, directory, or URL to a file identifying the replication controller to set a new size
Filename, directory, or URL to a file identifying the resource to set a new size
.PP
\fB\-o\fP, \fB\-\-output\fP=""
@ -148,16 +148,19 @@ scale is sent to the server.
.nf
# Scale replication controller named 'foo' to 3.
$ kubectl scale \-\-replicas=3 replicationcontrollers foo
$ kubectl scale \-\-replicas=3 rc/foo
# Scale a replication controller identified by type and name specified in "foo\-controller.yaml" to 3.
$ kubectl scale \-\-replicas=3 \-f foo\-controller.yaml
# Scale a resource identified by type and name specified in "foo.yaml" to 3.
$ kubectl scale \-\-replicas=3 \-f foo.yaml
# If the replication controller named foo's current size is 2, scale foo to 3.
$ kubectl scale \-\-current\-replicas=2 \-\-replicas=3 replicationcontrollers foo
# If the deployment named mysql's current size is 2, scale mysql to 3.
$ kubectl scale \-\-current\-replicas=2 \-\-replicas=3 deployment/mysql
# Scale multiple replication controllers.
$ kubectl scale \-\-replicas=5 rc/foo rc/bar
$ kubectl scale \-\-replicas=5 rc/foo rc/bar rc/baz
# Scale job named 'cron' to 3.
$ kubectl scale \-\-replicas=3 job/cron
.fi
.RE

View File

@ -101,10 +101,10 @@ kubectl
* [kubectl replace](kubectl_replace.md) - Replace a resource by filename or stdin.
* [kubectl rolling-update](kubectl_rolling-update.md) - Perform a rolling update of the given ReplicationController.
* [kubectl run](kubectl_run.md) - Run a particular image on the cluster.
* [kubectl scale](kubectl_scale.md) - Set a new size for a Replication Controller.
* [kubectl scale](kubectl_scale.md) - Set a new size for a Replication Controller, Job, or Deployment.
* [kubectl version](kubectl_version.md) - Print the client and server version information.
###### Auto generated by spf13/cobra on 24-Nov-2015
###### Auto generated by spf13/cobra on 25-Nov-2015
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl.md?pixel)]()

View File

@ -33,12 +33,12 @@ Documentation for other releases can be found at
## kubectl scale
Set a new size for a Replication Controller.
Set a new size for a Replication Controller, Job, or Deployment.
### Synopsis
Set a new size for a Replication Controller.
Set a new size for a Replication Controller, Job, or Deployment.
Scale also allows users to specify one or more preconditions for the scale action.
If --current-replicas or --resource-version is specified, it is validated before the
@ -53,23 +53,26 @@ kubectl scale [--resource-version=version] [--current-replicas=count] --replicas
```
# Scale replication controller named 'foo' to 3.
$ kubectl scale --replicas=3 replicationcontrollers foo
$ kubectl scale --replicas=3 rc/foo
# Scale a replication controller identified by type and name specified in "foo-controller.yaml" to 3.
$ kubectl scale --replicas=3 -f foo-controller.yaml
# Scale a resource identified by type and name specified in "foo.yaml" to 3.
$ kubectl scale --replicas=3 -f foo.yaml
# If the replication controller named foo's current size is 2, scale foo to 3.
$ kubectl scale --current-replicas=2 --replicas=3 replicationcontrollers foo
# If the deployment named mysql's current size is 2, scale mysql to 3.
$ kubectl scale --current-replicas=2 --replicas=3 deployment/mysql
# Scale multiple replication controllers.
$ kubectl scale --replicas=5 rc/foo rc/bar
$ kubectl scale --replicas=5 rc/foo rc/bar rc/baz
# Scale job named 'cron' to 3.
$ kubectl scale --replicas=3 job/cron
```
### Options
```
--current-replicas=-1: Precondition for current size. Requires that the current size of the replication controller match this value in order to scale.
-f, --filename=[]: Filename, directory, or URL to a file identifying the replication controller to set a new size
--current-replicas=-1: Precondition for current size. Requires that the current size of the resource match this value in order to scale.
-f, --filename=[]: Filename, directory, or URL to a file identifying the resource to set a new size
-o, --output="": Output mode. Use "-o name" for shorter output (resource/name).
--replicas=-1: The new desired number of replicas. Required.
--resource-version="": Precondition for resource version. Requires that the current resource version match this value in order to scale.

View File

@ -219,6 +219,8 @@ runTests() {
hpa_min_field=".spec.minReplicas"
hpa_max_field=".spec.maxReplicas"
hpa_cpu_field=".spec.cpuUtilization.targetPercentage"
job_parallelism_field=".spec.parallelism"
deployment_replicas=".spec.replicas"
# Passing no arguments to create is an error
! kubectl create
@ -873,6 +875,23 @@ __EOF__
# Clean-up
kubectl delete rc redis-{master,slave} "${kube_flags[@]}"
### Scale a job
kubectl create -f docs/user-guide/job.yaml "${kube_flags[@]}"
# Command
kubectl scale --replicas=2 job/pi
# Post-condition: 2 replicas for pi
kube::test::get_object_assert 'job pi' "{{$job_parallelism_field}}" '2'
# Clean-up
kubectl delete job/pi "${kube_flags[@]}"
### Scale a deployment
kubectl create -f examples/extensions/deployment.yaml "${kube_flags[@]}"
# Command
kubectl scale --current-replicas=3 --replicas=1 deployment/nginx-deployment
# Post-condition: 1 replica for nginx-deployment
kube::test::get_object_assert 'deployment nginx-deployment' "{{$deployment_replicas}}" '1'
# Clean-up
kubectl delete deployment/nginx-deployment "${kube_flags[@]}"
### Expose replication controller as service
# Pre-condition: 2 replicas
kube::test::get_object_assert 'rc frontend' "{{$rc_replicas_field}}" '2'

View File

@ -18,9 +18,9 @@ package extensions
import (
"fmt"
"sort"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util/sets"
)
@ -65,3 +65,21 @@ func PodSelectorAsSelector(ps *PodSelector) (labels.Selector, error) {
sort.Sort(labels.ByKey(selector))
return selector, nil
}
// ScaleFromDeployment returns a scale subresource for a deployment.
func ScaleFromDeployment(deployment *Deployment) *Scale {
return &Scale{
ObjectMeta: api.ObjectMeta{
Name: deployment.Name,
Namespace: deployment.Namespace,
CreationTimestamp: deployment.CreationTimestamp,
},
Spec: ScaleSpec{
Replicas: deployment.Spec.Replicas,
},
Status: ScaleStatus{
Replicas: deployment.Status.Replicas,
Selector: deployment.Spec.Selector,
},
}
}

View File

@ -45,10 +45,10 @@ func ControllerHasDesiredReplicas(c Interface, controller *api.ReplicationContro
// JobHasDesiredParallelism returns a condition that will be true if the desired parallelism count
// for a job equals the current active counts or is less by an appropriate successful/unsuccessful count.
func JobHasDesiredParallelism(c Interface, job *extensions.Job) wait.ConditionFunc {
func JobHasDesiredParallelism(c ExtensionsInterface, job *extensions.Job) wait.ConditionFunc {
return func() (bool, error) {
job, err := c.Extensions().Jobs(job.Namespace).Get(job.Name)
job, err := c.Jobs(job.Namespace).Get(job.Name)
if err != nil {
return false, err
}
@ -62,3 +62,17 @@ func JobHasDesiredParallelism(c Interface, job *extensions.Job) wait.ConditionFu
return progress == 0, nil
}
}
// DeploymentHasDesiredReplicas returns a condition that will be true if and only if
// the desired replica count for a deployment equals its updated replicas count.
// (non-terminated pods that have the desired template spec).
func DeploymentHasDesiredReplicas(c ExtensionsInterface, deployment *extensions.Deployment) wait.ConditionFunc {
return func() (bool, error) {
deployment, err := c.Deployments(deployment.Namespace).Get(deployment.Name)
if err != nil {
return false, err
}
return deployment.Status.UpdatedReplicas == deployment.Spec.Replicas, nil
}
}

View File

@ -315,6 +315,11 @@ func (c *Fake) SwaggerSchema(version string) (*swagger.ApiDeclaration, error) {
return &swagger.ApiDeclaration{}, nil
}
// NewSimpleFakeExp returns a client that will respond with the provided objects
func NewSimpleFakeExp(objects ...runtime.Object) *FakeExperimental {
return &FakeExperimental{Fake: NewSimpleFake(objects...)}
}
type FakeExperimental struct {
*Fake
}

View File

@ -36,23 +36,26 @@ type ScaleOptions struct {
}
const (
scale_long = `Set a new size for a Replication Controller.
scale_long = `Set a new size for a Replication Controller, Job, or Deployment.
Scale also allows users to specify one or more preconditions for the scale action.
If --current-replicas or --resource-version is specified, it is validated before the
scale is attempted, and it is guaranteed that the precondition holds true when the
scale is sent to the server.`
scale_example = `# Scale replication controller named 'foo' to 3.
$ kubectl scale --replicas=3 replicationcontrollers foo
$ kubectl scale --replicas=3 rc/foo
# Scale a replication controller identified by type and name specified in "foo-controller.yaml" to 3.
$ kubectl scale --replicas=3 -f foo-controller.yaml
# Scale a resource identified by type and name specified in "foo.yaml" to 3.
$ kubectl scale --replicas=3 -f foo.yaml
# If the replication controller named foo's current size is 2, scale foo to 3.
$ kubectl scale --current-replicas=2 --replicas=3 replicationcontrollers foo
# If the deployment named mysql's current size is 2, scale mysql to 3.
$ kubectl scale --current-replicas=2 --replicas=3 deployment/mysql
# Scale multiple replication controllers.
$ kubectl scale --replicas=5 rc/foo rc/bar`
$ kubectl scale --replicas=5 rc/foo rc/bar rc/baz
# Scale job named 'cron' to 3.
$ kubectl scale --replicas=3 job/cron`
)
// NewCmdScale returns a cobra command with the appropriate configuration and flags to run scale
@ -63,7 +66,7 @@ func NewCmdScale(f *cmdutil.Factory, out io.Writer) *cobra.Command {
Use: "scale [--resource-version=version] [--current-replicas=count] --replicas=COUNT (-f FILENAME | TYPE NAME)",
// resize is deprecated
Aliases: []string{"resize"},
Short: "Set a new size for a Replication Controller.",
Short: "Set a new size for a Replication Controller, Job, or Deployment.",
Long: scale_long,
Example: scale_example,
Run: func(cmd *cobra.Command, args []string) {
@ -74,13 +77,13 @@ func NewCmdScale(f *cmdutil.Factory, out io.Writer) *cobra.Command {
},
}
cmd.Flags().String("resource-version", "", "Precondition for resource version. Requires that the current resource version match this value in order to scale.")
cmd.Flags().Int("current-replicas", -1, "Precondition for current size. Requires that the current size of the replication controller match this value in order to scale.")
cmd.Flags().Int("current-replicas", -1, "Precondition for current size. Requires that the current size of the resource match this value in order to scale.")
cmd.Flags().Int("replicas", -1, "The new desired number of replicas. Required.")
cmd.MarkFlagRequired("replicas")
cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait.")
cmdutil.AddOutputFlagsForMutation(cmd)
usage := "Filename, directory, or URL to a file identifying the replication controller to set a new size"
usage := "Filename, directory, or URL to a file identifying the resource to set a new size"
kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
return cmd
}
@ -127,11 +130,11 @@ func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri
resourceVersion := cmdutil.GetFlagString(cmd, "resource-version")
if len(resourceVersion) != 0 && len(infos) > 1 {
return fmt.Errorf("cannot use --resource-version with multiple controllers")
return fmt.Errorf("cannot use --resource-version with multiple resources")
}
currentSize := cmdutil.GetFlagInt(cmd, "current-replicas")
if currentSize != -1 && len(infos) > 1 {
return fmt.Errorf("cannot use --current-replicas with multiple controllers")
return fmt.Errorf("cannot use --current-replicas with multiple resources")
}
precondition := &kubectl.ScalePrecondition{Size: currentSize, ResourceVersion: resourceVersion}
retry := kubectl.NewRetryParams(kubectl.Interval, kubectl.Timeout)

View File

@ -28,74 +28,7 @@ import (
"k8s.io/kubernetes/pkg/util/wait"
)
// ScalePrecondition describes a condition that must be true for the scale to take place
// If CurrentSize == -1, it is ignored.
// If CurrentResourceVersion is the empty string, it is ignored.
// Otherwise they must equal the values in the replication controller for it to be valid.
type ScalePrecondition struct {
Size int
ResourceVersion string
}
// A PreconditionError is returned when a replication controller fails to match
// the scale preconditions passed to kubectl.
type PreconditionError struct {
Precondition string
ExpectedValue string
ActualValue string
}
func (pe PreconditionError) Error() string {
return fmt.Sprintf("Expected %s to be %s, was %s", pe.Precondition, pe.ExpectedValue, pe.ActualValue)
}
type ControllerScaleErrorType int
const (
ControllerScaleGetFailure ControllerScaleErrorType = iota
ControllerScaleUpdateFailure
ControllerScaleUpdateInvalidFailure
)
// A ControllerScaleError is returned when a scale request passes
// preconditions but fails to actually scale the controller.
type ControllerScaleError struct {
FailureType ControllerScaleErrorType
ResourceVersion string
ActualError error
}
func (c ControllerScaleError) Error() string {
return fmt.Sprintf(
"Scaling the controller failed with: %s; Current resource version %s",
c.ActualError, c.ResourceVersion)
}
// ValidateReplicationController ensures that the preconditions match. Returns nil if they are valid, an error otherwise
func (precondition *ScalePrecondition) ValidateReplicationController(controller *api.ReplicationController) error {
if precondition.Size != -1 && controller.Spec.Replicas != precondition.Size {
return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(controller.Spec.Replicas)}
}
if precondition.ResourceVersion != "" && controller.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, controller.ResourceVersion}
}
return nil
}
// ValidateJob ensures that the preconditions match. Returns nil if they are valid, an error otherwise
func (precondition *ScalePrecondition) ValidateJob(job *extensions.Job) error {
if precondition.Size != -1 && job.Spec.Parallelism == nil {
return PreconditionError{"parallelism", strconv.Itoa(precondition.Size), "nil"}
}
if precondition.Size != -1 && *job.Spec.Parallelism != precondition.Size {
return PreconditionError{"parallelism", strconv.Itoa(precondition.Size), strconv.Itoa(*job.Spec.Parallelism)}
}
if precondition.ResourceVersion != "" && job.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, job.ResourceVersion}
}
return nil
}
// Scaler provides an interface for resources that can be scaled.
type Scaler interface {
// Scale scales the named resource after checking preconditions. It optionally
// retries in the event of resource version mismatch (if retry is not nil),
@ -111,16 +44,54 @@ func ScalerFor(kind string, c client.Interface) (Scaler, error) {
case "ReplicationController":
return &ReplicationControllerScaler{c}, nil
case "Job":
return &JobScaler{c}, nil
return &JobScaler{c.Extensions()}, nil
case "Deployment":
return &DeploymentScaler{c.Extensions()}, nil
}
return nil, fmt.Errorf("no scaler has been implemented for %q", kind)
}
type ReplicationControllerScaler struct {
c client.Interface
// ScalePrecondition describes a condition that must be true for the scale to take place
// If CurrentSize == -1, it is ignored.
// If CurrentResourceVersion is the empty string, it is ignored.
// Otherwise they must equal the values in the resource for it to be valid.
type ScalePrecondition struct {
Size int
ResourceVersion string
}
type JobScaler struct {
c client.Interface
// A PreconditionError is returned when a resource fails to match
// the scale preconditions passed to kubectl.
type PreconditionError struct {
Precondition string
ExpectedValue string
ActualValue string
}
func (pe PreconditionError) Error() string {
return fmt.Sprintf("Expected %s to be %s, was %s", pe.Precondition, pe.ExpectedValue, pe.ActualValue)
}
type ScaleErrorType int
const (
ScaleGetFailure ScaleErrorType = iota
ScaleUpdateFailure
ScaleUpdateInvalidFailure
)
// A ScaleError is returned when a scale request passes
// preconditions but fails to actually scale the controller.
type ScaleError struct {
FailureType ScaleErrorType
ResourceVersion string
ActualError error
}
func (c ScaleError) Error() string {
return fmt.Sprintf(
"Scaling the resource failed with: %v; Current resource version %s",
c.ActualError, c.ResourceVersion)
}
// RetryParams encapsulates the retry parameters used by kubectl's scaler.
@ -136,15 +107,15 @@ func NewRetryParams(interval, timeout time.Duration) *RetryParams {
func ScaleCondition(r Scaler, precondition *ScalePrecondition, namespace, name string, count uint) wait.ConditionFunc {
return func() (bool, error) {
err := r.ScaleSimple(namespace, name, precondition, count)
switch e, _ := err.(ControllerScaleError); err.(type) {
switch e, _ := err.(ScaleError); err.(type) {
case nil:
return true, nil
case ControllerScaleError:
case ScaleError:
// if it's invalid we shouldn't keep waiting
if e.FailureType == ControllerScaleUpdateInvalidFailure {
if e.FailureType == ScaleUpdateInvalidFailure {
return false, err
}
if e.FailureType == ControllerScaleUpdateFailure {
if e.FailureType == ScaleUpdateFailure {
return false, nil
}
}
@ -152,10 +123,25 @@ func ScaleCondition(r Scaler, precondition *ScalePrecondition, namespace, name s
}
}
// ValidateReplicationController ensures that the preconditions match. Returns nil if they are valid, an error otherwise
func (precondition *ScalePrecondition) ValidateReplicationController(controller *api.ReplicationController) error {
if precondition.Size != -1 && controller.Spec.Replicas != precondition.Size {
return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(controller.Spec.Replicas)}
}
if len(precondition.ResourceVersion) != 0 && controller.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, controller.ResourceVersion}
}
return nil
}
type ReplicationControllerScaler struct {
c client.Interface
}
func (scaler *ReplicationControllerScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) error {
controller, err := scaler.c.ReplicationControllers(namespace).Get(name)
if err != nil {
return ControllerScaleError{ControllerScaleGetFailure, "Unknown", err}
return ScaleError{ScaleGetFailure, "Unknown", err}
}
if preconditions != nil {
if err := preconditions.ValidateReplicationController(controller); err != nil {
@ -166,11 +152,10 @@ func (scaler *ReplicationControllerScaler) ScaleSimple(namespace, name string, p
// TODO: do retry on 409 errors here?
if _, err := scaler.c.ReplicationControllers(namespace).Update(controller); err != nil {
if errors.IsInvalid(err) {
return ControllerScaleError{ControllerScaleUpdateInvalidFailure, controller.ResourceVersion, err}
return ScaleError{ScaleUpdateInvalidFailure, controller.ResourceVersion, err}
}
return ControllerScaleError{ControllerScaleUpdateFailure, controller.ResourceVersion, err}
return ScaleError{ScaleUpdateFailure, controller.ResourceVersion, err}
}
// TODO: do a better job of printing objects here.
return nil
}
@ -200,11 +185,29 @@ func (scaler *ReplicationControllerScaler) Scale(namespace, name string, newSize
return nil
}
// ValidateJob ensures that the preconditions match. Returns nil if they are valid, an error otherwise.
func (precondition *ScalePrecondition) ValidateJob(job *extensions.Job) error {
if precondition.Size != -1 && job.Spec.Parallelism == nil {
return PreconditionError{"parallelism", strconv.Itoa(precondition.Size), "nil"}
}
if precondition.Size != -1 && *job.Spec.Parallelism != precondition.Size {
return PreconditionError{"parallelism", strconv.Itoa(precondition.Size), strconv.Itoa(*job.Spec.Parallelism)}
}
if len(precondition.ResourceVersion) != 0 && job.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, job.ResourceVersion}
}
return nil
}
type JobScaler struct {
c client.ExtensionsInterface
}
// ScaleSimple is responsible for updating job's parallelism.
func (scaler *JobScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) error {
job, err := scaler.c.Extensions().Jobs(namespace).Get(name)
job, err := scaler.c.Jobs(namespace).Get(name)
if err != nil {
return ControllerScaleError{ControllerScaleGetFailure, "Unknown", err}
return ScaleError{ScaleGetFailure, "Unknown", err}
}
if preconditions != nil {
if err := preconditions.ValidateJob(job); err != nil {
@ -213,12 +216,11 @@ func (scaler *JobScaler) ScaleSimple(namespace, name string, preconditions *Scal
}
parallelism := int(newSize)
job.Spec.Parallelism = &parallelism
if _, err := scaler.c.Extensions().Jobs(namespace).Update(job); err != nil {
if _, err := scaler.c.Jobs(namespace).Update(job); err != nil {
if errors.IsInvalid(err) {
return ControllerScaleError{ControllerScaleUpdateInvalidFailure, job.ResourceVersion, err}
return ScaleError{ScaleUpdateInvalidFailure, job.ResourceVersion, err}
}
return ControllerScaleError{ControllerScaleUpdateFailure, job.ResourceVersion, err}
return ScaleError{ScaleUpdateFailure, job.ResourceVersion, err}
}
return nil
}
@ -239,7 +241,7 @@ func (scaler *JobScaler) Scale(namespace, name string, newSize uint, preconditio
return err
}
if waitForReplicas != nil {
job, err := scaler.c.Extensions().Jobs(namespace).Get(name)
job, err := scaler.c.Jobs(namespace).Get(name)
if err != nil {
return err
}
@ -248,3 +250,65 @@ func (scaler *JobScaler) Scale(namespace, name string, newSize uint, preconditio
}
return nil
}
// ValidateDeployment ensures that the preconditions match. Returns nil if they are valid, an error otherwise.
func (precondition *ScalePrecondition) ValidateDeployment(deployment *extensions.Deployment) error {
if precondition.Size != -1 && deployment.Spec.Replicas != precondition.Size {
return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(deployment.Spec.Replicas)}
}
if len(precondition.ResourceVersion) != 0 && deployment.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, deployment.ResourceVersion}
}
return nil
}
type DeploymentScaler struct {
c client.ExtensionsInterface
}
// ScaleSimple is responsible for updating a deployment's desired replicas count.
func (scaler *DeploymentScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint) error {
deployment, err := scaler.c.Deployments(namespace).Get(name)
if err != nil {
return ScaleError{ScaleGetFailure, "Unknown", err}
}
if preconditions != nil {
if err := preconditions.ValidateDeployment(deployment); err != nil {
return err
}
}
scale := extensions.ScaleFromDeployment(deployment)
scale.Spec.Replicas = int(newSize)
if _, err := scaler.c.Scales(namespace).Update("Deployment", scale); err != nil {
if errors.IsInvalid(err) {
return ScaleError{ScaleUpdateInvalidFailure, deployment.ResourceVersion, err}
}
return ScaleError{ScaleUpdateFailure, deployment.ResourceVersion, err}
}
return nil
}
// Scale updates a deployment to a new size, with optional precondition check (if preconditions is not nil),
// optional retries (if retry is not nil), and then optionally waits for the status to reach desired count.
func (scaler *DeploymentScaler) Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams) error {
if preconditions == nil {
preconditions = &ScalePrecondition{-1, ""}
}
if retry == nil {
// Make it try only once, immediately
retry = &RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
}
cond := ScaleCondition(scaler, preconditions, namespace, name, newSize)
if err := wait.Poll(retry.Interval, retry.Timeout, cond); err != nil {
return err
}
if waitForReplicas != nil {
deployment, err := scaler.c.Deployments(namespace).Get(name)
if err != nil {
return err
}
return wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout,
client.DeploymentHasDesiredReplicas(scaler.c, deployment))
}
return nil
}

View File

@ -48,45 +48,6 @@ func (c *ErrorReplicationControllerClient) ReplicationControllers(namespace stri
return &ErrorReplicationControllers{testclient.FakeReplicationControllers{Fake: &c.Fake, Namespace: namespace}, c.invalid}
}
type ErrorJobs struct {
testclient.FakeJobs
invalid bool
}
func (c *ErrorJobs) Update(job *extensions.Job) (*extensions.Job, error) {
if c.invalid {
return nil, kerrors.NewInvalid(job.Kind, job.Name, nil)
}
return nil, errors.New("Job update failure")
}
func (c *ErrorJobs) Get(name string) (*extensions.Job, error) {
zero := 0
return &extensions.Job{
Spec: extensions.JobSpec{
Parallelism: &zero,
},
}, nil
}
type ErrorJobClient struct {
testclient.FakeExperimental
invalid bool
}
func (c *ErrorJobClient) Jobs(namespace string) client.JobInterface {
return &ErrorJobs{testclient.FakeJobs{Fake: &c.FakeExperimental, Namespace: namespace}, c.invalid}
}
type ErrorExtensionsClient struct {
testclient.Fake
invalid bool
}
func (c *ErrorExtensionsClient) Extensions() client.ExtensionsInterface {
return &ErrorJobClient{testclient.FakeExperimental{&c.Fake}, c.invalid}
}
func TestReplicationControllerScaleRetry(t *testing.T) {
fake := &ErrorReplicationControllerClient{Fake: testclient.Fake{}, invalid: false}
scaler := ReplicationControllerScaler{fake}
@ -124,8 +85,8 @@ func TestReplicationControllerScaleInvalid(t *testing.T) {
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
}
e, ok := err.(ControllerScaleError)
if err == nil || !ok || e.FailureType != ControllerScaleUpdateInvalidFailure {
e, ok := err.(ScaleError)
if err == nil || !ok || e.FailureType != ScaleUpdateInvalidFailure {
t.Errorf("Expected error on invalid update failure, got %v", err)
}
}
@ -286,8 +247,38 @@ func TestValidateReplicationController(t *testing.T) {
}
}
type ErrorJobs struct {
testclient.FakeJobs
invalid bool
}
func (c *ErrorJobs) Update(job *extensions.Job) (*extensions.Job, error) {
if c.invalid {
return nil, kerrors.NewInvalid(job.Kind, job.Name, nil)
}
return nil, errors.New("Job update failure")
}
func (c *ErrorJobs) Get(name string) (*extensions.Job, error) {
zero := 0
return &extensions.Job{
Spec: extensions.JobSpec{
Parallelism: &zero,
},
}, nil
}
type ErrorJobClient struct {
testclient.FakeExperimental
invalid bool
}
func (c *ErrorJobClient) Jobs(namespace string) client.JobInterface {
return &ErrorJobs{testclient.FakeJobs{Fake: &c.FakeExperimental, Namespace: namespace}, c.invalid}
}
func TestJobScaleRetry(t *testing.T) {
fake := &ErrorExtensionsClient{Fake: testclient.Fake{}, invalid: false}
fake := &ErrorJobClient{FakeExperimental: testclient.FakeExperimental{}, invalid: false}
scaler := JobScaler{fake}
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
@ -311,7 +302,7 @@ func TestJobScaleRetry(t *testing.T) {
}
func TestJobScale(t *testing.T) {
fake := &testclient.Fake{}
fake := &testclient.FakeExperimental{Fake: &testclient.Fake{}}
scaler := JobScaler{fake}
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
@ -331,7 +322,7 @@ func TestJobScale(t *testing.T) {
}
func TestJobScaleInvalid(t *testing.T) {
fake := &ErrorExtensionsClient{Fake: testclient.Fake{}, invalid: true}
fake := &ErrorJobClient{FakeExperimental: testclient.FakeExperimental{}, invalid: true}
scaler := JobScaler{fake}
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
@ -343,8 +334,8 @@ func TestJobScaleInvalid(t *testing.T) {
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
}
e, ok := err.(ControllerScaleError)
if err == nil || !ok || e.FailureType != ControllerScaleUpdateInvalidFailure {
e, ok := err.(ScaleError)
if err == nil || !ok || e.FailureType != ScaleUpdateInvalidFailure {
t.Errorf("Expected error on invalid update failure, got %v", err)
}
}
@ -356,7 +347,7 @@ func TestJobScaleFailsPreconditions(t *testing.T) {
Parallelism: &ten,
},
})
scaler := JobScaler{fake}
scaler := JobScaler{&testclient.FakeExperimental{fake}}
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
@ -496,3 +487,267 @@ func TestValidateJob(t *testing.T) {
}
}
}
type ErrorScales struct {
testclient.FakeScales
invalid bool
}
func (c *ErrorScales) Update(kind string, scale *extensions.Scale) (*extensions.Scale, error) {
if c.invalid {
return nil, kerrors.NewInvalid(scale.Kind, scale.Name, nil)
}
return nil, errors.New("scale update failure")
}
func (c *ErrorScales) Get(kind, name string) (*extensions.Scale, error) {
return &extensions.Scale{
Spec: extensions.ScaleSpec{
Replicas: 0,
},
}, nil
}
type ErrorDeployments struct {
testclient.FakeDeployments
invalid bool
}
func (c *ErrorDeployments) Update(deployment *extensions.Deployment) (*extensions.Deployment, error) {
if c.invalid {
return nil, kerrors.NewInvalid(deployment.Kind, deployment.Name, nil)
}
return nil, errors.New("deployment update failure")
}
func (c *ErrorDeployments) Get(name string) (*extensions.Deployment, error) {
return &extensions.Deployment{
Spec: extensions.DeploymentSpec{
Replicas: 0,
},
}, nil
}
type ErrorDeploymentClient struct {
testclient.FakeExperimental
invalid bool
}
func (c *ErrorDeploymentClient) Deployments(namespace string) client.DeploymentInterface {
return &ErrorDeployments{testclient.FakeDeployments{Fake: &c.FakeExperimental, Namespace: namespace}, c.invalid}
}
func (c *ErrorDeploymentClient) Scales(namespace string) client.ScaleInterface {
return &ErrorScales{testclient.FakeScales{Fake: &c.FakeExperimental, Namespace: namespace}, c.invalid}
}
func TestDeploymentScaleRetry(t *testing.T) {
fake := &ErrorDeploymentClient{FakeExperimental: testclient.FakeExperimental{Fake: &testclient.Fake{}}, invalid: false}
scaler := &DeploymentScaler{fake}
preconditions := &ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, preconditions, namespace, name, count)
pass, err := scaleFunc()
if pass != false {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
}
if err != nil {
t.Errorf("Did not expect an error on update failure, got %v", err)
}
preconditions = &ScalePrecondition{3, ""}
scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count)
pass, err = scaleFunc()
if err == nil {
t.Errorf("Expected error on precondition failure")
}
}
func TestDeploymentScale(t *testing.T) {
fake := &testclient.FakeExperimental{Fake: &testclient.Fake{}}
scaler := DeploymentScaler{fake}
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
scaler.Scale("default", name, count, &preconditions, nil, nil)
actions := fake.Actions()
if len(actions) != 2 {
t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", actions)
}
if action, ok := actions[0].(testclient.GetAction); !ok || action.GetResource() != "deployments" || action.GetName() != name {
t.Errorf("unexpected action: %v, expected get-replicationController %s", actions[0], name)
}
// TODO: The testclient needs to support subresources
if action, ok := actions[1].(testclient.UpdateAction); !ok || action.GetResource() != "Deployment" || action.GetObject().(*extensions.Scale).Spec.Replicas != int(count) {
t.Errorf("unexpected action %v, expected update-deployment-scale with replicas = %d", actions[1], count)
}
}
func TestDeploymentScaleInvalid(t *testing.T) {
fake := &ErrorDeploymentClient{FakeExperimental: testclient.FakeExperimental{Fake: &testclient.Fake{}}, invalid: true}
scaler := DeploymentScaler{fake}
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(&scaler, &preconditions, namespace, name, count)
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
}
e, ok := err.(ScaleError)
if err == nil || !ok || e.FailureType != ScaleUpdateInvalidFailure {
t.Errorf("Expected error on invalid update failure, got %v", err)
}
}
func TestDeploymentScaleFailsPreconditions(t *testing.T) {
fake := testclient.NewSimpleFake(&extensions.Deployment{
Spec: extensions.DeploymentSpec{
Replicas: 10,
},
})
scaler := DeploymentScaler{&testclient.FakeExperimental{fake}}
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
scaler.Scale("default", name, count, &preconditions, nil, nil)
actions := fake.Actions()
if len(actions) != 1 {
t.Errorf("unexpected actions: %v, expected 1 actions (get)", actions)
}
if action, ok := actions[0].(testclient.GetAction); !ok || action.GetResource() != "deployments" || action.GetName() != name {
t.Errorf("unexpected action: %v, expected get-deployment %s", actions[0], name)
}
}
func TestValidateDeployment(t *testing.T) {
zero, ten, twenty := 0, 10, 20
tests := []struct {
preconditions ScalePrecondition
deployment extensions.Deployment
expectError bool
test string
}{
{
preconditions: ScalePrecondition{-1, ""},
expectError: false,
test: "defaults",
},
{
preconditions: ScalePrecondition{-1, ""},
deployment: extensions.Deployment{
ObjectMeta: api.ObjectMeta{
ResourceVersion: "foo",
},
Spec: extensions.DeploymentSpec{
Replicas: ten,
},
},
expectError: false,
test: "defaults 2",
},
{
preconditions: ScalePrecondition{0, ""},
deployment: extensions.Deployment{
ObjectMeta: api.ObjectMeta{
ResourceVersion: "foo",
},
Spec: extensions.DeploymentSpec{
Replicas: zero,
},
},
expectError: false,
test: "size matches",
},
{
preconditions: ScalePrecondition{-1, "foo"},
deployment: extensions.Deployment{
ObjectMeta: api.ObjectMeta{
ResourceVersion: "foo",
},
Spec: extensions.DeploymentSpec{
Replicas: ten,
},
},
expectError: false,
test: "resource version matches",
},
{
preconditions: ScalePrecondition{10, "foo"},
deployment: extensions.Deployment{
ObjectMeta: api.ObjectMeta{
ResourceVersion: "foo",
},
Spec: extensions.DeploymentSpec{
Replicas: ten,
},
},
expectError: false,
test: "both match",
},
{
preconditions: ScalePrecondition{10, "foo"},
deployment: extensions.Deployment{
ObjectMeta: api.ObjectMeta{
ResourceVersion: "foo",
},
Spec: extensions.DeploymentSpec{
Replicas: twenty,
},
},
expectError: true,
test: "size different",
},
{
preconditions: ScalePrecondition{10, "foo"},
deployment: extensions.Deployment{
ObjectMeta: api.ObjectMeta{
ResourceVersion: "foo",
},
},
expectError: true,
test: "no replicas",
},
{
preconditions: ScalePrecondition{10, "foo"},
deployment: extensions.Deployment{
ObjectMeta: api.ObjectMeta{
ResourceVersion: "bar",
},
Spec: extensions.DeploymentSpec{
Replicas: ten,
},
},
expectError: true,
test: "version different",
},
{
preconditions: ScalePrecondition{10, "foo"},
deployment: extensions.Deployment{
ObjectMeta: api.ObjectMeta{
ResourceVersion: "bar",
},
Spec: extensions.DeploymentSpec{
Replicas: twenty,
},
},
expectError: true,
test: "both different",
},
}
for _, test := range tests {
err := test.preconditions.ValidateDeployment(&test.deployment)
if err != nil && !test.expectError {
t.Errorf("unexpected error: %v (%s)", err, test.test)
}
if err == nil && test.expectError {
t.Errorf("unexpected non-error: %v (%s)", err, test.test)
}
}
}

View File

@ -132,20 +132,7 @@ func (r *ScaleREST) Get(ctx api.Context, name string) (runtime.Object, error) {
if err != nil {
return nil, errors.NewNotFound("scale", name)
}
return &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: deployment.Namespace,
CreationTimestamp: deployment.CreationTimestamp,
},
Spec: extensions.ScaleSpec{
Replicas: deployment.Spec.Replicas,
},
Status: extensions.ScaleStatus{
Replicas: deployment.Status.Replicas,
Selector: deployment.Spec.Selector,
},
}, nil
return extensions.ScaleFromDeployment(deployment), nil
}
func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
@ -170,18 +157,5 @@ func (r *ScaleREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object,
if err != nil {
return nil, false, errors.NewConflict("scale", scale.Name, err)
}
return &extensions.Scale{
ObjectMeta: api.ObjectMeta{
Name: deployment.Name,
Namespace: deployment.Namespace,
CreationTimestamp: deployment.CreationTimestamp,
},
Spec: extensions.ScaleSpec{
Replicas: deployment.Spec.Replicas,
},
Status: extensions.ScaleStatus{
Replicas: deployment.Status.Replicas,
Selector: deployment.Spec.Selector,
},
}, false, nil
return extensions.ScaleFromDeployment(deployment), false, nil
}