package service import ( "fmt" "strings" "github.com/pkg/errors" "github.com/docker/docker/api/types/swarm" swarmapi "github.com/moby/swarmkit/v2/api" "github.com/moby/swarmkit/v2/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/moby/swarmkit/blob/de950a7ed842c7b7e47e9451cde9bf8f96031894/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 { generic := make([]swarm.GenericResource, 0, len(genericRes)) 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 } func buildGenericResourceMap(genericRes []swarm.GenericResource) (map[string]swarm.GenericResource, error) { m := make(map[string]swarm.GenericResource) for _, res := range genericRes { if res.DiscreteResourceSpec == nil { return nil, fmt.Errorf("invalid generic-resource `%+v` for service task", res) } _, ok := m[res.DiscreteResourceSpec.Kind] if ok { return nil, fmt.Errorf("duplicate generic-resource `%+v` for service task", res.DiscreteResourceSpec.Kind) } m[res.DiscreteResourceSpec.Kind] = res } return m, nil } func buildGenericResourceList(genericRes map[string]swarm.GenericResource) []swarm.GenericResource { l := make([]swarm.GenericResource, 0, len(genericRes)) for _, res := range genericRes { l = append(l, res) } return l }