Merge pull request #89666 from julianvmodesto/kubectl-scale-dry-run

Support kubectl scale --dry-run=server|client

Kubernetes-commit: 4bba193bea805300dd64f73c89eafaf016770e7a
This commit is contained in:
Kubernetes Publisher 2020-04-11 10:45:47 -07:00
commit 1e031af268
6 changed files with 70 additions and 42 deletions

4
Godeps/Godeps.json generated
View file

@ -576,7 +576,7 @@
},
{
"ImportPath": "k8s.io/apimachinery",
"Rev": "ff54c5b023af"
"Rev": "e002472249f8"
},
{
"ImportPath": "k8s.io/cli-runtime",
@ -584,7 +584,7 @@
},
{
"ImportPath": "k8s.io/client-go",
"Rev": "7b0589a2468d"
"Rev": "1be5940d0dd4"
},
{
"ImportPath": "k8s.io/code-generator",

8
go.mod
View file

@ -35,9 +35,9 @@ require (
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.0.0-20200410021914-5778e4f3d00d
k8s.io/apimachinery v0.0.0-20200410021338-ff54c5b023af
k8s.io/apimachinery v0.0.0-20200410061239-e002472249f8
k8s.io/cli-runtime v0.0.0-20200410032008-46db67004b57
k8s.io/client-go v0.0.0-20200410022504-7b0589a2468d
k8s.io/client-go v0.0.0-20200410182515-1be5940d0dd4
k8s.io/component-base v0.0.0-20200410024425-8582c5203e4b
k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20200403204345-e1beb1bd0f35
@ -52,9 +52,9 @@ replace (
golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // pinned to release-branch.go1.13
golang.org/x/tools => golang.org/x/tools v0.0.0-20190821162956-65e3620a7ae7 // pinned to release-branch.go1.13
k8s.io/api => k8s.io/api v0.0.0-20200410021914-5778e4f3d00d
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200410021338-ff54c5b023af
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200410061239-e002472249f8
k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20200410032008-46db67004b57
k8s.io/client-go => k8s.io/client-go v0.0.0-20200410022504-7b0589a2468d
k8s.io/client-go => k8s.io/client-go v0.0.0-20200410182515-1be5940d0dd4
k8s.io/code-generator => k8s.io/code-generator v0.0.0-20200410020940-986da3785c57
k8s.io/component-base => k8s.io/component-base v0.0.0-20200410024425-8582c5203e4b
k8s.io/metrics => k8s.io/metrics v0.0.0-20200410031643-1542edda33c0

4
go.sum
View file

@ -326,9 +326,9 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20200410021914-5778e4f3d00d/go.mod h1:kNZHsfUjF+wX4CfMBMSHs4FliofoB7PN0ILSMBbxyYc=
k8s.io/apimachinery v0.0.0-20200410021338-ff54c5b023af/go.mod h1:imoz42hIYwpLTRWXU8pdJ9IE8DbxUsnU9lyVN8Y1SNo=
k8s.io/apimachinery v0.0.0-20200410061239-e002472249f8/go.mod h1:imoz42hIYwpLTRWXU8pdJ9IE8DbxUsnU9lyVN8Y1SNo=
k8s.io/cli-runtime v0.0.0-20200410032008-46db67004b57/go.mod h1:tBOIBnD9aGgswoeK+7rN3kVzg/V058I/BQYITsFKjUQ=
k8s.io/client-go v0.0.0-20200410022504-7b0589a2468d/go.mod h1:VmRD5hiqfytf7+mAM5mYAhLdbpfs3musJzLrfOjpFXU=
k8s.io/client-go v0.0.0-20200410182515-1be5940d0dd4/go.mod h1:zXkQ9D1RJ94zQF7I0XpAPErYL57rqGlBnNP7CW3F2Uo=
k8s.io/code-generator v0.0.0-20200410020940-986da3785c57/go.mod h1:921XK/cUtrTpn/F0nAhNaFjo8I7Kue1BIEtdxsYFt9I=
k8s.io/component-base v0.0.0-20200410024425-8582c5203e4b/go.mod h1:grU+F0MqA5eYgNvtft1YM+zHsU4YH2WW18+1G97EZYQ=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=

View file

@ -89,6 +89,8 @@ type ScaleOptions struct {
scaler scale.Scaler
unstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
parent string
dryRunStrategy cmdutil.DryRunStrategy
dryRunVerifier *resource.DryRunVerifier
genericclioptions.IOStreams
}
@ -134,6 +136,7 @@ func NewCmdScale(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobr
cmd.MarkFlagRequired("replicas")
cmd.Flags().DurationVar(&o.Timeout, "timeout", 0, "The length of time to wait before giving up on a scale operation, zero means don't wait. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to set a new size")
cmdutil.AddDryRunFlag(cmd)
return cmd
}
@ -150,6 +153,20 @@ func (o *ScaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st
}
o.PrintObj = printer.PrintObj
o.dryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
if err != nil {
return err
}
dynamicClient, err := f.DynamicClient()
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.dryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
@ -216,7 +233,7 @@ func (o *ScaleOptions) RunScale() error {
retry := scale.NewRetryParams(1*time.Second, 5*time.Minute)
var waitForReplicas *scale.RetryParams
if o.Timeout != 0 {
if o.Timeout != 0 && o.dryRunStrategy == cmdutil.DryRunNone {
waitForReplicas = scale.NewRetryParams(1*time.Second, timeout)
}
@ -225,9 +242,13 @@ func (o *ScaleOptions) RunScale() error {
if err != nil {
return err
}
counter++
mapping := info.ResourceMapping()
if err := o.scaler.Scale(info.Namespace, info.Name, uint(o.Replicas), precondition, retry, waitForReplicas, mapping.Resource); err != nil {
if o.dryRunStrategy == cmdutil.DryRunClient {
return o.PrintObj(info.Object, o.Out)
}
if err := o.scaler.Scale(info.Namespace, info.Name, uint(o.Replicas), precondition, retry, waitForReplicas, mapping.Resource, o.dryRunStrategy == cmdutil.DryRunServer); err != nil {
return err
}
@ -245,7 +266,6 @@ func (o *ScaleOptions) RunScale() error {
}
}
counter++
return o.PrintObj(info.Object, o.Out)
})
if err != nil {

View file

@ -37,10 +37,10 @@ type Scaler interface {
// 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, gvr schema.GroupVersionResource) error
Scale(namespace, name string, newSize uint, preconditions *ScalePrecondition, retry, wait *RetryParams, gvr schema.GroupVersionResource, dryRun bool) 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, gvr schema.GroupVersionResource) (updatedResourceVersion string, err error)
ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint, gvr schema.GroupVersionResource, dryRun bool) (updatedResourceVersion string, err error)
}
// NewScaler get a scaler for a given resource
@ -79,9 +79,9 @@ func NewRetryParams(interval, timeout time.Duration) *RetryParams {
}
// 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, gvr schema.GroupVersionResource) wait.ConditionFunc {
func ScaleCondition(r Scaler, precondition *ScalePrecondition, namespace, name string, count uint, updatedResourceVersion *string, gvr schema.GroupVersionResource, dryRun bool) wait.ConditionFunc {
return func() (bool, error) {
rv, err := r.ScaleSimple(namespace, name, precondition, count, gvr)
rv, err := r.ScaleSimple(namespace, name, precondition, count, gvr, dryRun)
if updatedResourceVersion != nil {
*updatedResourceVersion = rv
}
@ -115,7 +115,7 @@ type genericScaler struct {
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, gvr schema.GroupVersionResource) (updatedResourceVersion string, err error) {
func (s *genericScaler) ScaleSimple(namespace, name string, preconditions *ScalePrecondition, newSize uint, gvr schema.GroupVersionResource, dryRun bool) (updatedResourceVersion string, err error) {
if preconditions != nil {
scale, err := s.scaleNamespacer.Scales(namespace).Get(context.TODO(), gvr.GroupResource(), name, metav1.GetOptions{})
if err != nil {
@ -125,7 +125,11 @@ func (s *genericScaler) ScaleSimple(namespace, name string, preconditions *Scale
return "", err
}
scale.Spec.Replicas = int32(newSize)
updatedScale, err := s.scaleNamespacer.Scales(namespace).Update(context.TODO(), gvr.GroupResource(), scale, metav1.UpdateOptions{})
updateOptions := metav1.UpdateOptions{}
if dryRun {
updateOptions.DryRun = []string{metav1.DryRunAll}
}
updatedScale, err := s.scaleNamespacer.Scales(namespace).Update(context.TODO(), gvr.GroupResource(), scale, updateOptions)
if err != nil {
return "", err
}
@ -133,7 +137,11 @@ func (s *genericScaler) ScaleSimple(namespace, name string, preconditions *Scale
}
patch := []byte(fmt.Sprintf(`{"spec":{"replicas":%d}}`, newSize))
updatedScale, err := s.scaleNamespacer.Scales(namespace).Patch(context.TODO(), gvr, name, types.MergePatchType, patch, metav1.PatchOptions{})
patchOptions := metav1.PatchOptions{}
if dryRun {
patchOptions.DryRun = []string{metav1.DryRunAll}
}
updatedScale, err := s.scaleNamespacer.Scales(namespace).Patch(context.TODO(), gvr, name, types.MergePatchType, patch, patchOptions)
if err != nil {
return "", err
}
@ -142,12 +150,12 @@ func (s *genericScaler) ScaleSimple(namespace, name string, preconditions *Scale
// 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, gvr schema.GroupVersionResource) error {
func (s *genericScaler) Scale(namespace, resourceName string, newSize uint, preconditions *ScalePrecondition, retry, waitForReplicas *RetryParams, gvr schema.GroupVersionResource, dryRun bool) 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, gvr)
cond := ScaleCondition(s, preconditions, namespace, resourceName, newSize, nil, gvr, dryRun)
if err := wait.PollImmediate(retry.Interval, retry.Timeout, cond); err != nil {
return err
}

View file

@ -68,7 +68,7 @@ func TestReplicationControllerScaleRetry(t *testing.T) {
name := "foo-v1"
namespace := metav1.NamespaceDefault
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, rcgvr)
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, rcgvr, false)
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
@ -77,7 +77,7 @@ func TestReplicationControllerScaleRetry(t *testing.T) {
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, rcgvr)
scaleFunc = ScaleCondition(scaler, &preconditions, namespace, name, count, nil, rcgvr, false)
_, err = scaleFunc()
if err == nil {
t.Errorf("Expected error on precondition failure")
@ -104,7 +104,7 @@ func TestReplicationControllerScaleInvalid(t *testing.T) {
name := "foo-v1"
namespace := "default"
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, rcgvr)
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, rcgvr, false)
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
@ -129,7 +129,7 @@ func TestReplicationControllerScale(t *testing.T) {
scaler := NewScaler(scaleClient)
count := uint(3)
name := "foo-v1"
err := scaler.Scale("default", name, count, nil, nil, nil, rcgvr)
err := scaler.Scale("default", name, count, nil, nil, nil, rcgvr, false)
if err != nil {
t.Fatalf("unexpected error occurred = %v while scaling the resource", err)
@ -152,7 +152,7 @@ func TestReplicationControllerScaleFailsPreconditions(t *testing.T) {
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, rcgvr)
err := scaler.Scale("default", name, count, &preconditions, nil, nil, rcgvr, false)
if err == nil {
t.Fatal("expected to get an error but none was returned")
}
@ -178,7 +178,7 @@ func TestDeploymentScaleRetry(t *testing.T) {
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, deploygvr)
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, deploygvr, false)
pass, err := scaleFunc()
if pass != false {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
@ -187,7 +187,7 @@ func TestDeploymentScaleRetry(t *testing.T) {
t.Errorf("Did not expect an error on update failure, got %v", err)
}
preconditions := &ScalePrecondition{3, ""}
scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil, deploygvr)
scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil, deploygvr, false)
_, err = scaleFunc()
if err == nil {
t.Error("Expected error on precondition failure")
@ -209,7 +209,7 @@ func TestDeploymentScale(t *testing.T) {
scaler := NewScaler(scaleClient)
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, nil, nil, nil, deploygvr)
err := scaler.Scale("default", name, count, nil, nil, nil, deploygvr, false)
if err != nil {
t.Fatal(err)
}
@ -235,7 +235,7 @@ func TestDeploymentScaleInvalid(t *testing.T) {
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, deploygvr)
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, deploygvr, false)
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
@ -261,7 +261,7 @@ func TestDeploymentScaleFailsPreconditions(t *testing.T) {
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, deploygvr)
err := scaler.Scale("default", name, count, &preconditions, nil, nil, deploygvr, false)
if err == nil {
t.Fatal("exptected to get an error but none was returned")
}
@ -282,7 +282,7 @@ func TestStatefulSetScale(t *testing.T) {
scaler := NewScaler(scaleClient)
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, nil, nil, nil, stsgvr)
err := scaler.Scale("default", name, count, nil, nil, nil, stsgvr, false)
if err != nil {
t.Fatal(err)
}
@ -308,7 +308,7 @@ func TestStatefulSetScaleRetry(t *testing.T) {
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, stsgvr)
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, stsgvr, false)
pass, err := scaleFunc()
if pass != false {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
@ -317,7 +317,7 @@ func TestStatefulSetScaleRetry(t *testing.T) {
t.Errorf("Did not expect an error on update failure, got %v", err)
}
preconditions := &ScalePrecondition{3, ""}
scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil, stsgvr)
scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil, stsgvr, false)
_, err = scaleFunc()
if err == nil {
t.Error("Expected error on precondition failure")
@ -344,7 +344,7 @@ func TestStatefulSetScaleInvalid(t *testing.T) {
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, stsgvr)
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, stsgvr, false)
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
@ -370,7 +370,7 @@ func TestStatefulSetScaleFailsPreconditions(t *testing.T) {
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, stsgvr)
err := scaler.Scale("default", name, count, &preconditions, nil, nil, stsgvr, false)
if err == nil {
t.Fatal("expected to get an error but none was returned")
}
@ -391,7 +391,7 @@ func TestReplicaSetScale(t *testing.T) {
scaler := NewScaler(scaleClient)
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, nil, nil, nil, rsgvr)
err := scaler.Scale("default", name, count, nil, nil, nil, rsgvr, false)
if err != nil {
t.Fatal(err)
}
@ -417,7 +417,7 @@ func TestReplicaSetScaleRetry(t *testing.T) {
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, rsgvr)
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, rsgvr, false)
pass, err := scaleFunc()
if pass != false {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
@ -426,7 +426,7 @@ func TestReplicaSetScaleRetry(t *testing.T) {
t.Errorf("Did not expect an error on update failure, got %v", err)
}
preconditions := &ScalePrecondition{3, ""}
scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil, rsgvr)
scaleFunc = ScaleCondition(scaler, preconditions, namespace, name, count, nil, rsgvr, false)
_, err = scaleFunc()
if err == nil {
t.Error("Expected error on precondition failure")
@ -453,7 +453,7 @@ func TestReplicaSetScaleInvalid(t *testing.T) {
name := "foo"
namespace := "default"
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, rsgvr)
scaleFunc := ScaleCondition(scaler, nil, namespace, name, count, nil, rsgvr, false)
pass, err := scaleFunc()
if pass {
t.Errorf("Expected an update failure to return pass = false, got pass = %v", pass)
@ -479,7 +479,7 @@ func TestReplicaSetsGetterFailsPreconditions(t *testing.T) {
preconditions := ScalePrecondition{2, ""}
count := uint(3)
name := "foo"
err := scaler.Scale("default", name, count, &preconditions, nil, nil, rsgvr)
err := scaler.Scale("default", name, count, &preconditions, nil, nil, rsgvr, false)
if err == nil {
t.Fatal("expected to get an error but non was returned")
}
@ -575,7 +575,7 @@ func TestGenericScaleSimple(t *testing.T) {
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.targetGVR)
resVersion, err := target.ScaleSimple("default", scenario.resName, scenario.precondition, uint(scenario.newSize), scenario.targetGVR, false)
if scenario.expectError && err == nil {
t.Fatal("expected an error but was not returned")
@ -665,7 +665,7 @@ func TestGenericScale(t *testing.T) {
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.targetGVR)
err := target.Scale("default", scenario.resName, uint(scenario.newSize), scenario.precondition, nil, scenario.waitForReplicas, scenario.targetGVR, false)
if scenario.expectError && err == nil {
t.Fatal("expected an error but was not returned")