From 295b8cdf162e6eb9eb118c882975672db4f20a2c Mon Sep 17 00:00:00 2001 From: derekwaynecarr Date: Wed, 9 Sep 2015 17:22:56 -0400 Subject: [PATCH] Add requests and limits to kubectl run --- contrib/completions/bash/kubectl | 2 + docs/man/man1/kubectl-run.1 | 8 +++ docs/user-guide/kubectl/kubectl_run.md | 2 + pkg/kubectl/cmd/run.go | 2 + pkg/kubectl/run.go | 65 +++++++++++++++++-- pkg/kubectl/run_test.go | 87 ++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 4 deletions(-) diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 025b4c7afb1..22119714264 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -687,6 +687,7 @@ _kubectl_run() flags+=("--image=") flags+=("--labels=") two_word_flags+=("-l") + flags+=("--limits=") flags+=("--no-headers") flags+=("--output=") two_word_flags+=("-o") @@ -695,6 +696,7 @@ _kubectl_run() flags+=("--port=") flags+=("--replicas=") two_word_flags+=("-r") + flags+=("--requests=") flags+=("--restart=") flags+=("--show-all") flags+=("-a") diff --git a/docs/man/man1/kubectl-run.1 b/docs/man/man1/kubectl-run.1 index 1ba98e3898b..11869df0b6f 100644 --- a/docs/man/man1/kubectl-run.1 +++ b/docs/man/man1/kubectl-run.1 @@ -50,6 +50,10 @@ Creates a replication controller to manage the created container(s). \fB\-l\fP, \fB\-\-labels\fP="" Labels to apply to the pod(s). +.PP +\fB\-\-limits\fP="" + The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi' + .PP \fB\-\-no\-headers\fP=false When using the default output, don't print headers. @@ -76,6 +80,10 @@ Creates a replication controller to manage the created container(s). \fB\-r\fP, \fB\-\-replicas\fP=1 Number of replicas to create for this container. Default is 1. +.PP +\fB\-\-requests\fP="" + The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi' + .PP \fB\-\-restart\fP="Always" The restart policy for this Pod. Legal values [Always, OnFailure, Never]. If set to 'Always' a replication controller is created for this pod, if set to OnFailure or Never, only the Pod is created and \-\-replicas must be 1. Default 'Always' diff --git a/docs/user-guide/kubectl/kubectl_run.md b/docs/user-guide/kubectl/kubectl_run.md index 7a8cf4da6a9..06ea96c79ad 100644 --- a/docs/user-guide/kubectl/kubectl_run.md +++ b/docs/user-guide/kubectl/kubectl_run.md @@ -87,12 +87,14 @@ $ kubectl run nginx --image=nginx --command -- ... --hostport=-1: The host port mapping for the container port. To demonstrate a single-machine container. --image="": The image for the container to run. -l, --labels="": Labels to apply to the pod(s). + --limits="": The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi' --no-headers[=false]: When using the default output, don't print headers. -o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md]. --output-version="": Output the formatted object with the given version (default api-version). --overrides="": An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. --port=-1: The port that this container exposes. -r, --replicas=1: Number of replicas to create for this container. Default is 1. + --requests="": The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi' --restart="Always": The restart policy for this Pod. Legal values [Always, OnFailure, Never]. If set to 'Always' a replication controller is created for this pod, if set to OnFailure or Never, only the Pod is created and --replicas must be 1. Default 'Always' -a, --show-all[=false]: When printing, show all resources (default hide terminated pods.) --sort-by="": If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. 'ObjectMeta.Name'). The field in the API resource specified by this JSONPath expression must be an integer or a string. diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index fa48996e123..d79f40d3803 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -92,6 +92,8 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c cmd.Flags().Bool("attach", false, "If true, wait for the Pod to start running, and then attach to the Pod as if 'kubectl attach ...' were called. Default false, unless '-i/--interactive' is set, in which case the default is true.") cmd.Flags().String("restart", "Always", "The restart policy for this Pod. Legal values [Always, OnFailure, Never]. If set to 'Always' a replication controller is created for this pod, if set to OnFailure or Never, only the Pod is created and --replicas must be 1. Default 'Always'") cmd.Flags().Bool("command", false, "If true and extra arguments are present, use them as the 'command' field in the container, rather than the 'args' field which is the default.") + cmd.Flags().String("requests", "", "The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'") + cmd.Flags().String("limits", "", "The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi'") return cmd } diff --git a/pkg/kubectl/run.go b/pkg/kubectl/run.go index 233cbbe9ba9..be8e843f39f 100644 --- a/pkg/kubectl/run.go +++ b/pkg/kubectl/run.go @@ -22,6 +22,7 @@ import ( "strings" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util" ) @@ -42,9 +43,51 @@ func (BasicReplicationController) ParamNames() []GeneratorParam { {"command", false}, {"args", false}, {"env", false}, + {"requests", false}, + {"limits", false}, } } +// populateResourceList takes strings of form =,= +func populateResourceList(spec string) (api.ResourceList, error) { + // empty input gets a nil response to preserve generator test expected behaviors + if spec == "" { + return nil, nil + } + + result := api.ResourceList{} + resourceStatements := strings.Split(spec, ",") + for _, resourceStatement := range resourceStatements { + parts := strings.Split(resourceStatement, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid argument syntax %v, expected =", resourceStatement) + } + resourceName := api.ResourceName(parts[0]) + resourceQuantity, err := resource.ParseQuantity(parts[1]) + if err != nil { + return nil, err + } + result[resourceName] = *resourceQuantity + } + return result, nil +} + +// HandleResourceRequirements parses the limits and requests parameters if specified +func HandleResourceRequirements(params map[string]string) (api.ResourceRequirements, error) { + result := api.ResourceRequirements{} + limits, err := populateResourceList(params["limits"]) + if err != nil { + return result, err + } + result.Limits = limits + requests, err := populateResourceList(params["requests"]) + if err != nil { + return result, err + } + result.Requests = requests + return result, nil +} + func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) { stdin, err := GetBool(params, "stdin", false) if err != nil { @@ -56,13 +99,19 @@ func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) { return nil, err } + resourceRequirements, err := HandleResourceRequirements(params) + if err != nil { + return nil, err + } + spec := api.PodSpec{ Containers: []api.Container{ { - Name: name, - Image: params["image"], - Stdin: stdin, - TTY: tty, + Name: name, + Image: params["image"], + Stdin: stdin, + TTY: tty, + Resources: resourceRequirements, }, }, } @@ -223,6 +272,8 @@ func (BasicPod) ParamNames() []GeneratorParam { {"command", false}, {"args", false}, {"env", false}, + {"requests", false}, + {"limits", false}, } } @@ -288,6 +339,11 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, return nil, err } + resourceRequirements, err := HandleResourceRequirements(params) + if err != nil { + return nil, err + } + restartPolicy := api.RestartPolicy(params["restart"]) if len(restartPolicy) == 0 { restartPolicy = api.RestartPolicyAlways @@ -305,6 +361,7 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, ImagePullPolicy: api.PullIfNotPresent, Stdin: stdin, TTY: tty, + Resources: resourceRequirements, }, }, DNSPolicy: api.DNSClusterFirst, diff --git a/pkg/kubectl/run_test.go b/pkg/kubectl/run_test.go index 3b187eba801..e16f3edd329 100644 --- a/pkg/kubectl/run_test.go +++ b/pkg/kubectl/run_test.go @@ -21,6 +21,7 @@ import ( "testing" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/resource" ) func TestGenerate(t *testing.T) { @@ -286,6 +287,92 @@ func TestGenerate(t *testing.T) { }, }, }, + { + params: map[string]interface{}{ + "name": "foo", + "image": "someimage", + "replicas": "1", + "hostport": "80", + }, + expected: nil, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": "foo", + "image": "someimage", + "replicas": "1", + "labels": "foo=bar,baz=blah", + "requests": "cpu100m,memory=100Mi", + }, + expected: nil, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": "foo", + "image": "someimage", + "replicas": "1", + "labels": "foo=bar,baz=blah", + "requests": "cpu=100m&memory=100Mi", + }, + expected: nil, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": "foo", + "image": "someimage", + "replicas": "1", + "labels": "foo=bar,baz=blah", + "requests": "cpu=", + }, + expected: nil, + expectErr: true, + }, + { + params: map[string]interface{}{ + "name": "foo", + "image": "someimage", + "replicas": "1", + "labels": "foo=bar,baz=blah", + "requests": "cpu=100m,memory=100Mi", + "limits": "cpu=400m,memory=200Mi", + }, + expected: &api.ReplicationController{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + Labels: map[string]string{"foo": "bar", "baz": "blah"}, + }, + Spec: api.ReplicationControllerSpec{ + Replicas: 1, + Selector: map[string]string{"foo": "bar", "baz": "blah"}, + Template: &api.PodTemplateSpec{ + ObjectMeta: api.ObjectMeta{ + Labels: map[string]string{"foo": "bar", "baz": "blah"}, + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "foo", + Image: "someimage", + Resources: api.ResourceRequirements{ + Requests: api.ResourceList{ + api.ResourceCPU: resource.MustParse("100m"), + api.ResourceMemory: resource.MustParse("100Mi"), + }, + Limits: api.ResourceList{ + api.ResourceCPU: resource.MustParse("400m"), + api.ResourceMemory: resource.MustParse("200Mi"), + }, + }, + }, + }, + }, + }, + }, + }, + }, } generator := BasicReplicationController{} for _, test := range tests {