Merge pull request #80772 from seans3/move-scale-updater-staging

Refactor pkg/kubectl/{scale.go|rollingupdater.go} for move to staging
This commit is contained in:
Kubernetes Prow Robot
2019-07-31 06:56:35 -07:00
committed by GitHub
16 changed files with 171 additions and 106 deletions

View File

@@ -1,70 +1,21 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = [
"rolling_updater_test.go",
"scale_test.go",
],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/client-go/scale:go_default_library",
"//staging/src/k8s.io/client-go/scale/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util:go_default_library",
],
)
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"conditions.go",
"doc.go",
"rolling_updater.go",
"scale.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/autoscaling/v1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/scale:go_default_library",
"//staging/src/k8s.io/client-go/util/retry:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/deployment:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/podutils:go_default_library",
"//vendor/k8s.io/utils/integer:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)
@@ -83,4 +34,5 @@ filegroup(
"//pkg/kubectl/explain:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -2,39 +2,72 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["rollingupdate.go"],
srcs = [
"rolling_updater.go",
"rollingupdate.go",
],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/rollingupdate",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/printers:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/resource:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/scale:go_default_library",
"//staging/src/k8s.io/client-go/util/retry:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scale:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/deployment:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/podutils:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/templates:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/validation:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/integer:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["rollingupdate_test.go"],
srcs = [
"rolling_updater_test.go",
"rollingupdate_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/kubectl/cmd/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scale:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scheme:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util:go_default_library",
],
)

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
package rollingupdate
import (
"fmt"
@@ -34,6 +34,7 @@ import (
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
scaleclient "k8s.io/client-go/scale"
"k8s.io/client-go/util/retry"
"k8s.io/kubectl/pkg/scale"
"k8s.io/kubectl/pkg/util"
deploymentutil "k8s.io/kubectl/pkg/util/deployment"
"k8s.io/kubectl/pkg/util/podutils"
@@ -128,7 +129,7 @@ type RollingUpdater struct {
// Namespace for resources
ns string
// scaleAndWait scales a controller and returns its updated state.
scaleAndWait func(rc *corev1.ReplicationController, retry *RetryParams, wait *RetryParams) (*corev1.ReplicationController, error)
scaleAndWait func(rc *corev1.ReplicationController, retry *scale.RetryParams, wait *scale.RetryParams) (*corev1.ReplicationController, error)
//getOrCreateTargetController gets and validates an existing controller or
//makes a new one.
getOrCreateTargetController func(controller *corev1.ReplicationController, sourceID string) (*corev1.ReplicationController, bool, error)
@@ -180,7 +181,7 @@ func NewRollingUpdater(namespace string, rcClient corev1client.ReplicationContro
func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error {
out := config.Out
oldRc := config.OldRc
scaleRetryParams := NewRetryParams(config.Interval, config.Timeout)
scaleRetryParams := scale.NewRetryParams(config.Interval, config.Timeout)
// Find an existing controller (for continuing an interrupted update) or
// create a new one if necessary.
@@ -321,7 +322,7 @@ func (r *RollingUpdater) Update(config *RollingUpdaterConfig) error {
// scaleUp scales up newRc to desired by whatever increment is possible given
// the configured surge threshold. scaleUp will safely no-op as necessary when
// it detects redundancy or other relevant conditions.
func (r *RollingUpdater) scaleUp(newRc, oldRc *corev1.ReplicationController, desired, maxSurge, maxUnavailable int32, scaleRetryParams *RetryParams, config *RollingUpdaterConfig) (*corev1.ReplicationController, error) {
func (r *RollingUpdater) scaleUp(newRc, oldRc *corev1.ReplicationController, desired, maxSurge, maxUnavailable int32, scaleRetryParams *scale.RetryParams, config *RollingUpdaterConfig) (*corev1.ReplicationController, error) {
// If we're already at the desired, do nothing.
if valOrZero(newRc.Spec.Replicas) == desired {
return newRc, nil
@@ -398,7 +399,10 @@ func (r *RollingUpdater) scaleDown(newRc, oldRc *corev1.ReplicationController, d
}
// Perform the scale-down.
fmt.Fprintf(config.Out, "Scaling %s down to %d\n", oldRc.Name, valOrZero(oldRc.Spec.Replicas))
retryWait := &RetryParams{config.Interval, config.Timeout}
retryWait := &scale.RetryParams{
Interval: config.Interval,
Timeout: config.Timeout,
}
scaledRc, err := r.scaleAndWait(oldRc, retryWait, retryWait)
if err != nil {
return nil, err
@@ -407,9 +411,9 @@ func (r *RollingUpdater) scaleDown(newRc, oldRc *corev1.ReplicationController, d
}
// scalerScaleAndWait scales a controller using a Scaler and a real client.
func (r *RollingUpdater) scaleAndWaitWithScaler(rc *corev1.ReplicationController, retry *RetryParams, wait *RetryParams) (*corev1.ReplicationController, error) {
scaler := NewScaler(r.scaleClient)
if err := scaler.Scale(rc.Namespace, rc.Name, uint(valOrZero(rc.Spec.Replicas)), &ScalePrecondition{-1, ""}, retry, wait, schema.GroupResource{Resource: "replicationcontrollers"}); err != nil {
func (r *RollingUpdater) scaleAndWaitWithScaler(rc *corev1.ReplicationController, retry *scale.RetryParams, wait *scale.RetryParams) (*corev1.ReplicationController, error) {
scaler := scale.NewScaler(r.scaleClient)
if err := scaler.Scale(rc.Namespace, rc.Name, uint(valOrZero(rc.Spec.Replicas)), &scale.ScalePrecondition{Size: -1, ResourceVersion: ""}, retry, wait, schema.GroupResource{Resource: "replicationcontrollers"}); err != nil {
return nil, err
}
return r.rcClient.ReplicationControllers(rc.Namespace).Get(rc.Name, metav1.GetOptions{})
@@ -520,7 +524,7 @@ func (r *RollingUpdater) cleanupWithClients(oldRc, newRc *corev1.ReplicationCont
return err
}
if err = wait.Poll(config.Interval, config.Timeout, ControllerHasDesiredReplicas(r.rcClient, newRc)); err != nil {
if err = wait.Poll(config.Interval, config.Timeout, controllerHasDesiredReplicas(r.rcClient, newRc)); err != nil {
return err
}
newRc, err = r.rcClient.ReplicationControllers(r.ns).Get(newRc.Name, metav1.GetOptions{})
@@ -838,3 +842,24 @@ func FindSourceController(r corev1client.ReplicationControllersGetter, namespace
}
return nil, fmt.Errorf("couldn't find a replication controller with source id == %s/%s", namespace, name)
}
// controllerHasDesiredReplicas returns a condition that will be true if and only if
// the desired replica count for a controller's ReplicaSelector equals the Replicas count.
func controllerHasDesiredReplicas(rcClient corev1client.ReplicationControllersGetter, controller *corev1.ReplicationController) wait.ConditionFunc {
// If we're given a controller where the status lags the spec, it either means that the controller is stale,
// or that the rc manager hasn't noticed the update yet. Polling status.Replicas is not safe in the latter case.
desiredGeneration := controller.Generation
return func() (bool, error) {
ctrl, err := rcClient.ReplicationControllers(controller.Namespace).Get(controller.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
// There's a chance a concurrent update modifies the Spec.Replicas causing this check to pass,
// or, after this check has passed, a modification causes the rc manager to create more pods.
// This will not be an issue once we've implemented graceful delete for rcs, but till then
// concurrent stop operations on the same rc might have unintended side effects.
return ctrl.Status.ObservedGeneration >= desiredGeneration && ctrl.Status.Replicas == valOrZero(ctrl.Spec.Replicas), nil
}
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package kubectl
package rollingupdate
import (
"bytes"
@@ -40,6 +40,7 @@ import (
restclient "k8s.io/client-go/rest"
manualfake "k8s.io/client-go/rest/fake"
testcore "k8s.io/client-go/testing"
"k8s.io/kubectl/pkg/scale"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
)
@@ -790,7 +791,7 @@ Scaling foo-v2 up to 2
t.Logf("running test %d (%s) (up: %v, down: %v, oldReady: %v, newReady: %v)", i, tt.name, upTo, downTo, oldReady, newReady)
updater := &RollingUpdater{
ns: "default",
scaleAndWait: func(rc *corev1.ReplicationController, retry *RetryParams, wait *RetryParams) (*corev1.ReplicationController, error) {
scaleAndWait: func(rc *corev1.ReplicationController, retry *scale.RetryParams, wait *scale.RetryParams) (*corev1.ReplicationController, error) {
// Return a scale up or scale down expectation depending on the rc,
// and throw errors if there is no expectation expressed for this
// call.
@@ -861,7 +862,7 @@ func TestUpdate_progressTimeout(t *testing.T) {
newRc := newRc(0, 2)
updater := &RollingUpdater{
ns: "default",
scaleAndWait: func(rc *corev1.ReplicationController, retry *RetryParams, wait *RetryParams) (*corev1.ReplicationController, error) {
scaleAndWait: func(rc *corev1.ReplicationController, retry *scale.RetryParams, wait *scale.RetryParams) (*corev1.ReplicationController, error) {
// Do nothing.
return rc, nil
},
@@ -906,7 +907,7 @@ func TestUpdate_assignOriginalAnnotation(t *testing.T) {
rcClient: fake.CoreV1(),
podClient: fake.CoreV1(),
ns: "default",
scaleAndWait: func(rc *corev1.ReplicationController, retry *RetryParams, wait *RetryParams) (*corev1.ReplicationController, error) {
scaleAndWait: func(rc *corev1.ReplicationController, retry *scale.RetryParams, wait *scale.RetryParams) (*corev1.ReplicationController, error) {
return rc, nil
},
getOrCreateTargetController: func(controller *corev1.ReplicationController, sourceID string) (*corev1.ReplicationController, bool, error) {

View File

@@ -40,7 +40,6 @@ import (
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/validation"
"k8s.io/kubernetes/pkg/kubectl"
)
var (
@@ -255,11 +254,11 @@ func (o *RollingUpdateOptions) Run() error {
return err
}
// We're in the middle of a rename, look for an RC with a source annotation of oldName
newRc, err := kubectl.FindSourceController(coreClient, o.Namespace, o.OldName)
newRc, err := FindSourceController(coreClient, o.Namespace, o.OldName)
if err != nil {
return err
}
return kubectl.Rename(coreClient, newRc, o.OldName)
return Rename(coreClient, newRc, o.OldName)
}
var replicasDefaulted bool
@@ -311,7 +310,7 @@ func (o *RollingUpdateOptions) Run() error {
if len(o.Image) != 0 {
codec := scheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion)
newName := o.FindNewName(oldRc)
if newRc, err = kubectl.LoadExistingNextReplicationController(coreClient, o.Namespace, newName); err != nil {
if newRc, err = LoadExistingNextReplicationController(coreClient, o.Namespace, newName); err != nil {
return err
}
if newRc != nil {
@@ -320,7 +319,7 @@ func (o *RollingUpdateOptions) Run() error {
}
fmt.Fprintf(o.Out, "Found existing update in progress (%s), resuming.\n", newRc.Name)
} else {
config := &kubectl.NewControllerConfig{
config := &NewControllerConfig{
Namespace: o.Namespace,
OldName: o.OldName,
NewName: newName,
@@ -334,7 +333,7 @@ func (o *RollingUpdateOptions) Run() error {
}
config.PullPolicy = corev1.PullPolicy(o.PullPolicy)
}
newRc, err = kubectl.CreateNewControllerFromCurrentController(coreClient, codec, config)
newRc, err = CreateNewControllerFromCurrentController(coreClient, codec, config)
if err != nil {
return err
}
@@ -347,7 +346,7 @@ func (o *RollingUpdateOptions) Run() error {
}
// If new image is same as old, the hash may not be distinct, so add a suffix.
oldHash += "-orig"
oldRc, err = kubectl.UpdateExistingReplicationController(coreClient, coreClient, oldRc, o.Namespace, newRc.Name, o.DeploymentKey, oldHash, o.Out)
oldRc, err = UpdateExistingReplicationController(coreClient, coreClient, oldRc, o.Namespace, newRc.Name, o.DeploymentKey, oldHash, o.Out)
if err != nil {
return err
}
@@ -355,7 +354,7 @@ func (o *RollingUpdateOptions) Run() error {
if o.Rollback {
newName := o.FindNewName(oldRc)
if newRc, err = kubectl.LoadExistingNextReplicationController(coreClient, o.Namespace, newName); err != nil {
if newRc, err = LoadExistingNextReplicationController(coreClient, o.Namespace, newName); err != nil {
return err
}
@@ -369,7 +368,7 @@ func (o *RollingUpdateOptions) Run() error {
filename, o.OldName)
}
updater := kubectl.NewRollingUpdater(newRc.Namespace, coreClient, coreClient, o.ScaleClient)
updater := NewRollingUpdater(newRc.Namespace, coreClient, coreClient, o.ScaleClient)
// 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
@@ -412,11 +411,11 @@ func (o *RollingUpdateOptions) Run() error {
fmt.Fprintf(o.Out, "Rolling from:\n%s\nTo:\n%s\n", string(oldRcData.Bytes()), string(newRcData.Bytes()))
return nil
}
updateCleanupPolicy := kubectl.DeleteRollingUpdateCleanupPolicy
updateCleanupPolicy := DeleteRollingUpdateCleanupPolicy
if o.KeepOldName {
updateCleanupPolicy = kubectl.RenameRollingUpdateCleanupPolicy
updateCleanupPolicy = RenameRollingUpdateCleanupPolicy
}
config := &kubectl.RollingUpdaterConfig{
config := &RollingUpdaterConfig{
Out: o.Out,
OldRc: oldRc,
NewRc: newRc,
@@ -428,7 +427,7 @@ func (o *RollingUpdateOptions) Run() error {
MaxSurge: intstr.FromInt(1),
}
if o.Rollback {
err = kubectl.AbortRollingUpdate(config)
err = AbortRollingUpdate(config)
if err != nil {
return err
}
@@ -462,7 +461,7 @@ func findNewName(args []string, oldRc *corev1.ReplicationController) string {
return args[1]
}
if oldRc != nil {
newName, _ := kubectl.GetNextControllerAnnotation(oldRc)
newName, _ := GetNextControllerAnnotation(oldRc)
return newName
}
return ""

View File

@@ -6,7 +6,6 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/scale",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
@@ -14,6 +13,7 @@ go_library(
"//staging/src/k8s.io/cli-runtime/pkg/resource:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/cmd/util:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/scale:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/i18n:go_default_library",
"//staging/src/k8s.io/kubectl/pkg/util/templates:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",

View File

@@ -30,9 +30,9 @@ import (
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/kubernetes"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scale"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubernetes/pkg/kubectl"
)
var (
@@ -86,7 +86,7 @@ type ScaleOptions struct {
args []string
shortOutput bool
clientSet kubernetes.Interface
scaler kubectl.Scaler
scaler scale.Scaler
unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
parent string
@@ -209,15 +209,15 @@ func (o *ScaleOptions) RunScale() error {
// only set a precondition if the user has requested one. A nil precondition means we can do a blind update, so
// we avoid a Scale GET that may or may not succeed
var precondition *kubectl.ScalePrecondition
var precondition *scale.ScalePrecondition
if o.CurrentReplicas != -1 || len(o.ResourceVersion) > 0 {
precondition = &kubectl.ScalePrecondition{Size: o.CurrentReplicas, ResourceVersion: o.ResourceVersion}
precondition = &scale.ScalePrecondition{Size: o.CurrentReplicas, ResourceVersion: o.ResourceVersion}
}
retry := kubectl.NewRetryParams(1*time.Second, 5*time.Minute)
retry := scale.NewRetryParams(1*time.Second, 5*time.Minute)
var waitForReplicas *kubectl.RetryParams
var waitForReplicas *scale.RetryParams
if o.Timeout != 0 {
waitForReplicas = kubectl.NewRetryParams(1*time.Second, timeout)
waitForReplicas = scale.NewRetryParams(1*time.Second, timeout)
}
counter := 0
@@ -257,11 +257,11 @@ func (o *ScaleOptions) RunScale() error {
return nil
}
func scaler(f cmdutil.Factory) (kubectl.Scaler, error) {
func scaler(f cmdutil.Factory) (scale.Scaler, error) {
scalesGetter, err := cmdutil.ScaleClientFn(f)
if err != nil {
return nil, err
}
return kubectl.NewScaler(scalesGetter), nil
return scale.NewScaler(scalesGetter), nil
}

View File

@@ -28,6 +28,13 @@ import (
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
)
func valOrZero(val *int32) int32 {
if val == nil {
return int32(0)
}
return *val
}
// ControllerHasDesiredReplicas returns a condition that will be true if and only if
// the desired replica count for a controller's ReplicaSelector equals the Replicas count.
func ControllerHasDesiredReplicas(rcClient corev1client.ReplicationControllersGetter, controller *corev1.ReplicationController) wait.ConditionFunc {

View File

@@ -1,187 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors.
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 kubectl
import (
"fmt"
"strconv"
"time"
autoscalingv1 "k8s.io/api/autoscaling/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
scaleclient "k8s.io/client-go/scale"
)
// 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),
// and optionally waits until the status of the resource matches newSize (if wait is not nil)
// TODO: Make the implementation of this watch-based (#56075) once #31345 is fixed.
Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, wait *RetryParams, gr schema.GroupResource) error
// ScaleSimple does a simple one-shot attempt at scaling - not useful on its own, but
// a necessary building block for Scale
ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint, gr schema.GroupResource) (updatedResourceVersion string, err error)
}
// NewScaler get a scaler for a given resource
func NewScaler(scalesGetter scaleclient.ScalesGetter) Scaler {
return &genericScaler{scalesGetter}
}
// 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
}
// 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)
}
// RetryParams encapsulates the retry parameters used by kubectl's scaler.
type RetryParams struct {
Interval, Timeout time.Duration
}
func NewRetryParams(interval, timeout time.Duration) *RetryParams {
return &RetryParams{interval, timeout}
}
// ScaleCondition is a closure around Scale that facilitates retries via util.wait
func ScaleCondition(r Scaler, precondition *ScalePrecondition, namespace, name string, count uint, updatedResourceVersion *string, gr schema.GroupResource) wait.ConditionFunc {
return func() (bool, error) {
rv, err := r.ScaleSimple(namespace, name, precondition, count, gr)
if updatedResourceVersion != nil {
*updatedResourceVersion = rv
}
// Retry only on update conflicts.
if errors.IsConflict(err) {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
}
// validateGeneric ensures that the preconditions match. Returns nil if they are valid, otherwise an error
func (precondition *ScalePrecondition) validate(scale *autoscalingv1.Scale) error {
if precondition.Size != -1 && int(scale.Spec.Replicas) != precondition.Size {
return PreconditionError{"replicas", strconv.Itoa(precondition.Size), strconv.Itoa(int(scale.Spec.Replicas))}
}
if len(precondition.ResourceVersion) > 0 && scale.ResourceVersion != precondition.ResourceVersion {
return PreconditionError{"resource version", precondition.ResourceVersion, scale.ResourceVersion}
}
return nil
}
// genericScaler can update scales for resources in a particular namespace
type genericScaler struct {
scaleNamespacer scaleclient.ScalesGetter
}
var _ Scaler = &genericScaler{}
// ScaleSimple updates a scale of a given resource. It returns the resourceVersion of the scale if the update was successful.
func (s *genericScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint, gr schema.GroupResource) (updatedResourceVersion string, err error) {
scale := &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
}
if preconditions != nil {
var err error
scale, err = s.scaleNamespacer.Scales(namespace).Get(gr, name)
if err != nil {
return "", err
}
if err := preconditions.validate(scale); err != nil {
return "", err
}
}
scale.Spec.Replicas = int32(newSize)
updatedScale, err := s.scaleNamespacer.Scales(namespace).Update(gr, scale)
if err != nil {
return "", err
}
return updatedScale.ResourceVersion, nil
}
// Scale updates a scale of a given resource 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 (s *genericScaler) Scale(namespace, resourceName string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams, gr schema.GroupResource) error {
if retry == nil {
// make it try only once, immediately
retry = &RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
}
cond := ScaleCondition(s, preconditions, namespace, resourceName, newSize, nil, gr)
if err := wait.PollImmediate(retry.Interval, retry.Timeout, cond); err != nil {
return err
}
if waitForReplicas != nil {
return WaitForScaleHasDesiredReplicas(s.scaleNamespacer, gr, resourceName, namespace, newSize, waitForReplicas)
}
return nil
}
// scaleHasDesiredReplicas returns a condition that will be true if and only if the desired replica
// count for a scale (Spec) equals its updated replicas count (Status)
func scaleHasDesiredReplicas(sClient scaleclient.ScalesGetter, gr schema.GroupResource, resourceName string, namespace string, desiredReplicas int32) wait.ConditionFunc {
return func() (bool, error) {
actualScale, err := sClient.Scales(namespace).Get(gr, resourceName)
if err != nil {
return false, err
}
// this means the desired scale target has been reset by something else
if actualScale.Spec.Replicas != desiredReplicas {
return true, nil
}
return actualScale.Spec.Replicas == actualScale.Status.Replicas &&
desiredReplicas == actualScale.Status.Replicas, nil
}
}
// WaitForScaleHasDesiredReplicas waits until condition scaleHasDesiredReplicas is satisfied
// or returns error when timeout happens
func WaitForScaleHasDesiredReplicas(sClient scaleclient.ScalesGetter, gr schema.GroupResource, resourceName string, namespace string, newSize uint, waitForReplicas *RetryParams) error {
if waitForReplicas == nil {
return fmt.Errorf("waitForReplicas parameter cannot be nil")
}
err := wait.PollImmediate(
waitForReplicas.Interval,
waitForReplicas.Timeout,
scaleHasDesiredReplicas(sClient, gr, resourceName, namespace, int32(newSize)))
if err == wait.ErrWaitTimeout {
return fmt.Errorf("timed out waiting for %q to be synced", resourceName)
}
return err
}

View File

@@ -1,683 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors.
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 kubectl
import (
"fmt"
"testing"
"time"
autoscalingv1 "k8s.io/api/autoscaling/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/scale"
fakescale "k8s.io/client-go/scale/fake"
testcore "k8s.io/client-go/testing"
)
func TestReplicationControllerScaleRetry(t *testing.T) {
verbsOnError := map[string]*kerrors.StatusError{
"update": kerrors.NewConflict(api.Resource("Status"), "foo", nil),
}
scaleClientExpectedAction := []string{"get", "update", "get"}
scaleClient := createFakeScaleClient("replicationcontrollers", "foo-v1", 2, verbsOnError)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo-v1"
namespace := metav1.NamespaceDefault
scaleFunc := ScaleCondition(scaler, &preconditions, namespace, name, count, nil, schema.GroupResource{Group: "", Resource: "replicationcontrollers"})
pass, err := scaleFunc()
if pass {
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 conflict failure, got %v", err)
}
preconditions = ScalePrecondition{3, ""}
scaleFunc = ScaleCondition(scaler, &preconditions, namespace, name, count, nil, schema.GroupResource{Group: "", Resource: "replicationcontrollers"})
pass, err = scaleFunc()
if err == nil {
t.Errorf("Expected error on precondition failure")
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestReplicationControllerScaleInvalid(t *testing.T) {
verbsOnError := map[string]*kerrors.StatusError{
"update": kerrors.NewInvalid(api.Kind("Status"), "foo", nil),
}
scaleClientExpectedAction := []string{"get", "update"}
scaleClient := createFakeScaleClient("replicationcontrollers", "foo-v1", 1, verbsOnError)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo-v1"
namespace := "default"
scaleFunc := ScaleCondition(scaler, &preconditions, namespace, name, count, nil, schema.GroupResource{Group: "", Resource: "replicationcontrollers"})
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
}
if err == nil {
t.Errorf("Expected error on invalid update failure, got %v", err)
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestReplicationControllerScale(t *testing.T) {
scaleClientExpectedAction := []string{"get", "update"}
scaleClient := createFakeScaleClient("replicationcontrollers", "foo-v1", 2, nil)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo-v1"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, schema.GroupResource{Group: "", Resource: "replicationcontrollers"})
if err != nil {
t.Fatalf("unexpected error occurred = %v while scaling the resource", err)
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestReplicationControllerScaleFailsPreconditions(t *testing.T) {
scaleClientExpectedAction := []string{"get"}
scaleClient := createFakeScaleClient("replicationcontrollers", "foo", 10, nil)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, schema.GroupResource{Group: "", Resource: "replicationcontrollers"})
if err == nil {
t.Fatal("expected to get an error but none was returned")
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestDeploymentScaleRetry(t *testing.T) {
verbsOnError := map[string]*kerrors.StatusError{
"update": kerrors.NewConflict(api.Resource("Status"), "foo", nil),
}
scaleClientExpectedAction := []string{"get", "update", "get"}
scaleClient := createFakeScaleClient("deployments", "foo", 2, verbsOnError)
scaler := NewScaler(scaleClient)
preconditions := &ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, preconditions, namespace, name, count, nil, schema.GroupResource{Group: "apps", Resource: "deployments"})
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, nil, schema.GroupResource{Group: "apps", Resource: "deployments"})
pass, err = scaleFunc()
if err == nil {
t.Error("Expected error on precondition failure")
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestDeploymentScale(t *testing.T) {
scaleClientExpectedAction := []string{"get", "update"}
scaleClient := createFakeScaleClient("deployments", "foo", 2, nil)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, schema.GroupResource{Group: "apps", Resource: "deployments"})
if err != nil {
t.Fatal(err)
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestDeploymentScaleInvalid(t *testing.T) {
scaleClientExpectedAction := []string{"get", "update"}
verbsOnError := map[string]*kerrors.StatusError{
"update": kerrors.NewInvalid(api.Kind("Status"), "foo", nil),
}
scaleClient := createFakeScaleClient("deployments", "foo", 2, verbsOnError)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, &preconditions, namespace, name, count, nil, schema.GroupResource{Group: "apps", Resource: "deployments"})
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
}
if err == nil {
t.Errorf("Expected error on invalid update failure, got %v", err)
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestDeploymentScaleFailsPreconditions(t *testing.T) {
scaleClientExpectedAction := []string{"get"}
scaleClient := createFakeScaleClient("deployments", "foo", 10, nil)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, schema.GroupResource{Group: "apps", Resource: "deployments"})
if err == nil {
t.Fatal("exptected to get an error but none was returned")
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestStatefulSetScale(t *testing.T) {
scaleClientExpectedAction := []string{"get", "update"}
scaleClient := createFakeScaleClient("statefulsets", "foo", 2, nil)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, schema.GroupResource{Group: "apps", Resource: "statefulset"})
if err != nil {
t.Fatal(err)
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestStatefulSetScaleRetry(t *testing.T) {
scaleClientExpectedAction := []string{"get", "update", "get"}
verbsOnError := map[string]*kerrors.StatusError{
"update": kerrors.NewConflict(api.Resource("Status"), "foo", nil),
}
scaleClient := createFakeScaleClient("statefulsets", "foo", 2, verbsOnError)
scaler := NewScaler(scaleClient)
preconditions := &ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, preconditions, namespace, name, count, nil, schema.GroupResource{Group: "apps", Resource: "statefulsets"})
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, nil, schema.GroupResource{Group: "apps", Resource: "statefulsets"})
pass, err = scaleFunc()
if err == nil {
t.Error("Expected error on precondition failure")
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestStatefulSetScaleInvalid(t *testing.T) {
scaleClientExpectedAction := []string{"get", "update"}
verbsOnError := map[string]*kerrors.StatusError{
"update": kerrors.NewInvalid(api.Kind("Status"), "foo", nil),
}
scaleClient := createFakeScaleClient("statefulsets", "foo", 2, verbsOnError)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, &preconditions, namespace, name, count, nil, schema.GroupResource{Group: "apps", Resource: "statefulsets"})
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
}
if err == nil {
t.Errorf("Expected error on invalid update failure, got %v", err)
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestStatefulSetScaleFailsPreconditions(t *testing.T) {
scaleClientExpectedAction := []string{"get"}
scaleClient := createFakeScaleClient("statefulsets", "foo", 10, nil)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, schema.GroupResource{Group: "apps", Resource: "statefulsets"})
if err == nil {
t.Fatal("expected to get an error but none was returned")
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestReplicaSetScale(t *testing.T) {
scaleClientExpectedAction := []string{"get", "update"}
scaleClient := createFakeScaleClient("replicasets", "foo", 10, nil)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, schema.GroupResource{Group: "extensions", Resource: "replicasets"})
if err != nil {
t.Fatal(err)
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestReplicaSetScaleRetry(t *testing.T) {
verbsOnError := map[string]*kerrors.StatusError{
"update": kerrors.NewConflict(api.Resource("Status"), "foo", nil),
}
scaleClientExpectedAction := []string{"get", "update", "get"}
scaleClient := createFakeScaleClient("replicasets", "foo", 2, verbsOnError)
scaler := NewScaler(scaleClient)
preconditions := &ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, preconditions, namespace, name, count, nil, schema.GroupResource{Group: "extensions", Resource: "replicasets"})
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, nil, schema.GroupResource{Group: "extensions", Resource: "replicasets"})
pass, err = scaleFunc()
if err == nil {
t.Error("Expected error on precondition failure")
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestReplicaSetScaleInvalid(t *testing.T) {
verbsOnError := map[string]*kerrors.StatusError{
"update": kerrors.NewInvalid(api.Kind("Status"), "foo", nil),
}
scaleClientExpectedAction := []string{"get", "update"}
scaleClient := createFakeScaleClient("replicasets", "foo", 2, verbsOnError)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{-1, ""}
count := uint(3)
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, &preconditions, namespace, name, count, nil, schema.GroupResource{Group: "extensions", Resource: "replicasets"})
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
}
if err == nil {
t.Errorf("Expected error on invalid update failure, got %v", err)
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
func TestReplicaSetsGetterFailsPreconditions(t *testing.T) {
scaleClientExpectedAction := []string{"get"}
scaleClient := createFakeScaleClient("replicasets", "foo", 10, nil)
scaler := NewScaler(scaleClient)
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, schema.GroupResource{Group: "extensions", Resource: "replicasets"})
if err == nil {
t.Fatal("expected to get an error but non was returned")
}
actions := scaleClient.Actions()
if len(actions) != len(scaleClientExpectedAction) {
t.Errorf("unexpected actions: %v, expected %d actions got %d", actions, len(scaleClientExpectedAction), len(actions))
}
for i, verb := range scaleClientExpectedAction {
if actions[i].GetVerb() != verb {
t.Errorf("unexpected action: %+v, expected %s", actions[i].GetVerb(), verb)
}
}
}
// TestGenericScaleSimple exercises GenericScaler.ScaleSimple method
func TestGenericScaleSimple(t *testing.T) {
// test data
scaleClient := createFakeScaleClient("deployments", "abc", 10, nil)
// test scenarios
scenarios := []struct {
name string
precondition ScalePrecondition
newSize int
targetGR schema.GroupResource
resName string
scaleGetter scale.ScalesGetter
expectError bool
}{
// scenario 1: scale up the "abc" deployment
{
name: "scale up the \"abc\" deployment",
precondition: ScalePrecondition{10, ""},
newSize: 20,
targetGR: schema.GroupResource{Group: "apps", Resource: "deployments"},
resName: "abc",
scaleGetter: scaleClient,
},
// scenario 2: scale down the "abc" deployment
{
name: "scale down the \"abs\" deployment",
precondition: ScalePrecondition{20, ""},
newSize: 5,
targetGR: schema.GroupResource{Group: "apps", Resource: "deployments"},
resName: "abc",
scaleGetter: scaleClient,
},
// scenario 3: precondition error, expected size is 1,
// note that the previous scenario (2) set the size to 5
{
name: "precondition error, expected size is 1",
precondition: ScalePrecondition{1, ""},
newSize: 5,
targetGR: schema.GroupResource{Group: "apps", Resource: "deployments"},
resName: "abc",
scaleGetter: scaleClient,
expectError: true,
},
// scenario 4: precondition is not validated when the precondition size is set to -1
{
name: "precondition is not validated when the size is set to -1",
precondition: ScalePrecondition{-1, ""},
newSize: 5,
targetGR: schema.GroupResource{Group: "apps", Resource: "deployments"},
resName: "abc",
scaleGetter: scaleClient,
},
// scenario 5: precondition error, resource version mismatch
{
name: "precondition error, resource version mismatch",
precondition: ScalePrecondition{5, "v1"},
newSize: 5,
targetGR: schema.GroupResource{Group: "apps", Resource: "deployments"},
resName: "abc",
scaleGetter: scaleClient,
expectError: true,
},
}
// act
for index, scenario := range scenarios {
t.Run(fmt.Sprintf("running scenario %d: %s", index+1, scenario.name), func(t *testing.T) {
target := NewScaler(scenario.scaleGetter)
resVersion, err := target.ScaleSimple("default", scenario.resName, &scenario.precondition, uint(scenario.newSize), scenario.targetGR)
if scenario.expectError && err == nil {
t.Fatal("expected an error but was not returned")
}
if !scenario.expectError && err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resVersion != "" {
t.Fatalf("unexpected resource version returned = %s, wanted = %s", resVersion, "")
}
})
}
}
// TestGenericScale exercises GenericScaler.Scale method
func TestGenericScale(t *testing.T) {
// test data
scaleClient := createFakeScaleClient("deployments", "abc", 10, nil)
// test scenarios
scenarios := []struct {
name string
precondition ScalePrecondition
newSize int
targetGR schema.GroupResource
resName string
scaleGetter scale.ScalesGetter
waitForReplicas *RetryParams
expectError bool
}{
// scenario 1: scale up the "abc" deployment
{
name: "scale up the \"abc\" deployment",
precondition: ScalePrecondition{10, ""},
newSize: 20,
targetGR: schema.GroupResource{Group: "apps", Resource: "deployments"},
resName: "abc",
scaleGetter: scaleClient,
},
//scenario 2: a resource name cannot be empty
{
name: "a resource name cannot be empty",
precondition: ScalePrecondition{10, ""},
newSize: 20,
targetGR: schema.GroupResource{Group: "apps", Resource: "deployments"},
resName: "",
scaleGetter: scaleClient,
expectError: true,
},
// scenario 3: wait for replicas error due to status.Replicas != spec.Replicas
{
name: "wait for replicas error due to status.Replicas != spec.Replicas",
precondition: ScalePrecondition{10, ""},
newSize: 20,
targetGR: schema.GroupResource{Group: "apps", Resource: "deployments"},
resName: "abc",
scaleGetter: scaleClient,
waitForReplicas: &RetryParams{time.Duration(5 * time.Second), time.Duration(5 * time.Second)},
expectError: true,
},
}
// act
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
target := NewScaler(scenario.scaleGetter)
err := target.Scale("default", scenario.resName, uint(scenario.newSize), &scenario.precondition, nil, scenario.waitForReplicas, scenario.targetGR)
if scenario.expectError && err == nil {
t.Fatal("expected an error but was not returned")
}
if !scenario.expectError && err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
}
func createFakeScaleClient(resource string, resourceName string, replicas int, errorsOnVerb map[string]*kerrors.StatusError) *fakescale.FakeScaleClient {
shouldReturnAnError := func(verb string) (*kerrors.StatusError, bool) {
if anError, anErrorExists := errorsOnVerb[verb]; anErrorExists {
return anError, true
}
return &kerrors.StatusError{}, false
}
newReplicas := int32(replicas)
scaleClient := &fakescale.FakeScaleClient{}
scaleClient.AddReactor("get", resource, func(rawAction testcore.Action) (handled bool, ret runtime.Object, err error) {
action := rawAction.(testcore.GetAction)
if action.GetName() != resourceName {
return true, nil, fmt.Errorf("expected = %s, got = %s", resourceName, action.GetName())
}
if anError, should := shouldReturnAnError("get"); should {
return true, nil, anError
}
obj := &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Name: action.GetName(),
Namespace: action.GetNamespace(),
},
Spec: autoscalingv1.ScaleSpec{
Replicas: newReplicas,
},
}
return true, obj, nil
})
scaleClient.AddReactor("update", resource, func(rawAction testcore.Action) (handled bool, ret runtime.Object, err error) {
action := rawAction.(testcore.UpdateAction)
obj := action.GetObject().(*autoscalingv1.Scale)
if obj.Name != resourceName {
return true, nil, fmt.Errorf("expected = %s, got = %s", resourceName, obj.Name)
}
if anError, should := shouldReturnAnError("update"); should {
return true, nil, anError
}
newReplicas = obj.Spec.Replicas
return true, &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Name: obj.Name,
Namespace: action.GetNamespace(),
},
Spec: autoscalingv1.ScaleSpec{
Replicas: newReplicas,
},
}, nil
})
return scaleClient
}