package resourceapi import ( "errors" "time" "github.com/docker/swarmkit/api" "github.com/docker/swarmkit/ca" "github.com/docker/swarmkit/identity" "github.com/docker/swarmkit/manager/state/store" "github.com/docker/swarmkit/protobuf/ptypes" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) var ( errInvalidArgument = errors.New("invalid argument") ) // ResourceAllocator handles resource allocation of cluster entities. type ResourceAllocator struct { store *store.MemoryStore } // New returns an instance of the allocator func New(store *store.MemoryStore) *ResourceAllocator { return &ResourceAllocator{store: store} } // AttachNetwork allows the node to request the resources // allocation needed for a network attachment on the specific node. // - Returns `InvalidArgument` if the Spec is malformed. // - Returns `NotFound` if the Network is not found. // - Returns `PermissionDenied` if the Network is not manually attachable. // - Returns an error if the creation fails. func (ra *ResourceAllocator) AttachNetwork(ctx context.Context, request *api.AttachNetworkRequest) (*api.AttachNetworkResponse, error) { nodeInfo, err := ca.RemoteNode(ctx) if err != nil { return nil, err } var network *api.Network ra.store.View(func(tx store.ReadTx) { network = store.GetNetwork(tx, request.Config.Target) if network == nil { if networks, err := store.FindNetworks(tx, store.ByName(request.Config.Target)); err == nil && len(networks) == 1 { network = networks[0] } } }) if network == nil { return nil, grpc.Errorf(codes.NotFound, "network %s not found", request.Config.Target) } if !network.Spec.Attachable { return nil, grpc.Errorf(codes.PermissionDenied, "network %s not manually attachable", request.Config.Target) } t := &api.Task{ ID: identity.NewID(), NodeID: nodeInfo.NodeID, Spec: api.TaskSpec{ Runtime: &api.TaskSpec_Attachment{ Attachment: &api.NetworkAttachmentSpec{ ContainerID: request.ContainerID, }, }, Networks: []*api.NetworkAttachmentConfig{ { Target: network.ID, Addresses: request.Config.Addresses, }, }, }, Status: api.TaskStatus{ State: api.TaskStateNew, Timestamp: ptypes.MustTimestampProto(time.Now()), Message: "created", }, DesiredState: api.TaskStateRunning, // TODO: Add Network attachment. } if err := ra.store.Update(func(tx store.Tx) error { return store.CreateTask(tx, t) }); err != nil { return nil, err } return &api.AttachNetworkResponse{AttachmentID: t.ID}, nil } // DetachNetwork allows the node to request the release of // the resources associated to the network attachment. // - Returns `InvalidArgument` if attachment ID is not provided. // - Returns `NotFound` if the attachment is not found. // - Returns an error if the deletion fails. func (ra *ResourceAllocator) DetachNetwork(ctx context.Context, request *api.DetachNetworkRequest) (*api.DetachNetworkResponse, error) { if request.AttachmentID == "" { return nil, grpc.Errorf(codes.InvalidArgument, errInvalidArgument.Error()) } nodeInfo, err := ca.RemoteNode(ctx) if err != nil { return nil, err } if err := ra.store.Update(func(tx store.Tx) error { t := store.GetTask(tx, request.AttachmentID) if t == nil { return grpc.Errorf(codes.NotFound, "attachment %s not found", request.AttachmentID) } if t.NodeID != nodeInfo.NodeID { return grpc.Errorf(codes.PermissionDenied, "attachment %s doesn't belong to this node", request.AttachmentID) } return store.DeleteTask(tx, request.AttachmentID) }); err != nil { return nil, err } return &api.DetachNetworkResponse{}, nil }