Updated GenericResource CLI

Signed-off-by: Renaud Gaubert <renaud.gaubert@gmail.com>
This commit is contained in:
Renaud Gaubert 2017-11-08 17:33:36 +01:00 committed by Renaud Gaubert
parent 7843aec98c
commit 7ddd5f3434
3 changed files with 101 additions and 9 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
cliopts "github.com/docker/cli/opts"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/spf13/cobra" "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.Var(&opts.hosts, flagHost, "Set one or more custom host-to-IP mappings (host:ip)")
flags.SetAnnotation(flagHost, "version", []string{"1.25"}) 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) flags.SetInterspersed(false)
return cmd return cmd
} }

View File

@ -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
}

View File

@ -226,9 +226,15 @@ type resourceOptions struct {
limitMemBytes opts.MemBytes limitMemBytes opts.MemBytes
resCPU opts.NanoCPUs resCPU opts.NanoCPUs
resMemBytes opts.MemBytes 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{ return &swarm.ResourceRequirements{
Limits: &swarm.Resources{ Limits: &swarm.Resources{
NanoCPUs: r.limitCPU.Value(), NanoCPUs: r.limitCPU.Value(),
@ -237,8 +243,9 @@ func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
Reservations: &swarm.Resources{ Reservations: &swarm.Resources{
NanoCPUs: r.resCPU.Value(), NanoCPUs: r.resCPU.Value(),
MemoryBytes: r.resMemBytes.Value(), MemoryBytes: r.resMemBytes.Value(),
GenericResources: generic,
}, },
} }, nil
} }
type restartPolicyOptions struct { type restartPolicyOptions struct {
@ -588,6 +595,11 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
return service, err return service, err
} }
resources, err := options.resources.ToResourceRequirements()
if err != nil {
return service, err
}
service = swarm.ServiceSpec{ service = swarm.ServiceSpec{
Annotations: swarm.Annotations{ Annotations: swarm.Annotations{
Name: options.name, Name: options.name,
@ -619,7 +631,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
Isolation: container.Isolation(options.isolation), Isolation: container.Isolation(options.isolation),
}, },
Networks: networks, Networks: networks,
Resources: options.resources.ToResourceRequirements(), Resources: resources,
RestartPolicy: options.restartPolicy.ToRestartPolicy(flags), RestartPolicy: options.restartPolicy.ToRestartPolicy(flags),
Placement: &swarm.Placement{ Placement: &swarm.Placement{
Constraints: options.constraints.GetAll(), Constraints: options.constraints.GetAll(),