From 7ddd5f3434470cec983f319c5dc27c7a3fb6085b Mon Sep 17 00:00:00 2001 From: Renaud Gaubert Date: Wed, 8 Nov 2017 17:33:36 +0100 Subject: [PATCH] Updated GenericResource CLI Signed-off-by: Renaud Gaubert --- cli/command/service/create.go | 4 ++ cli/command/service/generic_resource_opts.go | 76 ++++++++++++++++++++ cli/command/service/opts.go | 30 +++++--- 3 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 cli/command/service/generic_resource_opts.go diff --git a/cli/command/service/create.go b/cli/command/service/create.go index 84944fd23c..299cdc383f 100644 --- a/cli/command/service/create.go +++ b/cli/command/service/create.go @@ -5,6 +5,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + cliopts "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/spf13/cobra" @@ -58,6 +59,9 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command { flags.Var(&opts.hosts, flagHost, "Set one or more custom host-to-IP mappings (host:ip)") flags.SetAnnotation(flagHost, "version", []string{"1.25"}) + flags.Var(cliopts.NewListOptsRef(&opts.resources.resGenericResources, ValidateSingleGenericResource), "generic-resource", "User defined resources") + flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"}) + flags.SetInterspersed(false) return cmd } diff --git a/cli/command/service/generic_resource_opts.go b/cli/command/service/generic_resource_opts.go new file mode 100644 index 0000000000..5effe83968 --- /dev/null +++ b/cli/command/service/generic_resource_opts.go @@ -0,0 +1,76 @@ +package service + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + + "github.com/docker/docker/api/types/swarm" + swarmapi "github.com/docker/swarmkit/api" + "github.com/docker/swarmkit/api/genericresource" +) + +// GenericResource is a concept that a user can use to advertise user-defined +// resources on a node and thus better place services based on these resources. +// E.g: NVIDIA GPUs, Intel FPGAs, ... +// See https://github.com/docker/swarmkit/blob/master/design/generic_resources.md + +// ValidateSingleGenericResource validates that a single entry in the +// generic resource list is valid. +// i.e 'GPU=UID1' is valid however 'GPU:UID1' or 'UID1' isn't +func ValidateSingleGenericResource(val string) (string, error) { + if strings.Count(val, "=") < 1 { + return "", fmt.Errorf("invalid generic-resource format `%s` expected `name=value`", val) + } + + return val, nil +} + +// ParseGenericResources parses an array of Generic resourceResources +// Requesting Named Generic Resources for a service is not supported this +// is filtered here. +func ParseGenericResources(value []string) ([]swarm.GenericResource, error) { + if len(value) == 0 { + return nil, nil + } + + resources, err := genericresource.Parse(value) + if err != nil { + return nil, errors.Wrapf(err, "invalid generic resource specification") + } + + swarmResources := genericResourcesFromGRPC(resources) + for _, res := range swarmResources { + if res.NamedResourceSpec != nil { + return nil, fmt.Errorf("invalid generic-resource request `%s=%s`, Named Generic Resources is not supported for service create or update", res.NamedResourceSpec.Kind, res.NamedResourceSpec.Value) + } + } + + return swarmResources, nil +} + +// genericResourcesFromGRPC converts a GRPC GenericResource to a GenericResource +func genericResourcesFromGRPC(genericRes []*swarmapi.GenericResource) []swarm.GenericResource { + var generic []swarm.GenericResource + for _, res := range genericRes { + var current swarm.GenericResource + + switch r := res.Resource.(type) { + case *swarmapi.GenericResource_DiscreteResourceSpec: + current.DiscreteResourceSpec = &swarm.DiscreteGenericResource{ + Kind: r.DiscreteResourceSpec.Kind, + Value: r.DiscreteResourceSpec.Value, + } + case *swarmapi.GenericResource_NamedResourceSpec: + current.NamedResourceSpec = &swarm.NamedGenericResource{ + Kind: r.NamedResourceSpec.Kind, + Value: r.NamedResourceSpec.Value, + } + } + + generic = append(generic, current) + } + + return generic +} diff --git a/cli/command/service/opts.go b/cli/command/service/opts.go index 071998607e..209167dfda 100644 --- a/cli/command/service/opts.go +++ b/cli/command/service/opts.go @@ -222,23 +222,30 @@ func (opts updateOptions) rollbackConfig(flags *pflag.FlagSet) *swarm.UpdateConf } type resourceOptions struct { - limitCPU opts.NanoCPUs - limitMemBytes opts.MemBytes - resCPU opts.NanoCPUs - resMemBytes opts.MemBytes + limitCPU opts.NanoCPUs + limitMemBytes opts.MemBytes + resCPU opts.NanoCPUs + resMemBytes opts.MemBytes + resGenericResources []string } -func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements { +func (r *resourceOptions) ToResourceRequirements() (*swarm.ResourceRequirements, error) { + generic, err := ParseGenericResources(r.resGenericResources) + if err != nil { + return nil, err + } + return &swarm.ResourceRequirements{ Limits: &swarm.Resources{ NanoCPUs: r.limitCPU.Value(), MemoryBytes: r.limitMemBytes.Value(), }, Reservations: &swarm.Resources{ - NanoCPUs: r.resCPU.Value(), - MemoryBytes: r.resMemBytes.Value(), + NanoCPUs: r.resCPU.Value(), + MemoryBytes: r.resMemBytes.Value(), + GenericResources: generic, }, - } + }, nil } type restartPolicyOptions struct { @@ -588,6 +595,11 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N return service, err } + resources, err := options.resources.ToResourceRequirements() + if err != nil { + return service, err + } + service = swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: options.name, @@ -619,7 +631,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N Isolation: container.Isolation(options.isolation), }, Networks: networks, - Resources: options.resources.ToResourceRequirements(), + Resources: resources, RestartPolicy: options.restartPolicy.ToRestartPolicy(flags), Placement: &swarm.Placement{ Constraints: options.constraints.GetAll(),