blob: 481b0cfe4b9ee6a5bfd266c001f586e21b1ae647 [file] [log] [blame]
package controlapi
import (
"net"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/identity"
"github.com/docker/swarmkit/manager/allocator"
"github.com/docker/swarmkit/manager/allocator/networkallocator"
"github.com/docker/swarmkit/manager/state/store"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func validateIPAMConfiguration(ipamConf *api.IPAMConfig) error {
if ipamConf == nil {
return status.Errorf(codes.InvalidArgument, "ipam configuration: cannot be empty")
}
_, subnet, err := net.ParseCIDR(ipamConf.Subnet)
if err != nil {
return status.Errorf(codes.InvalidArgument, "ipam configuration: invalid subnet %s", ipamConf.Subnet)
}
if ipamConf.Range != "" {
ip, _, err := net.ParseCIDR(ipamConf.Range)
if err != nil {
return status.Errorf(codes.InvalidArgument, "ipam configuration: invalid range %s", ipamConf.Range)
}
if !subnet.Contains(ip) {
return status.Errorf(codes.InvalidArgument, "ipam configuration: subnet %s does not contain range %s", ipamConf.Subnet, ipamConf.Range)
}
}
if ipamConf.Gateway != "" {
ip := net.ParseIP(ipamConf.Gateway)
if ip == nil {
return status.Errorf(codes.InvalidArgument, "ipam configuration: invalid gateway %s", ipamConf.Gateway)
}
if !subnet.Contains(ip) {
return status.Errorf(codes.InvalidArgument, "ipam configuration: subnet %s does not contain gateway %s", ipamConf.Subnet, ipamConf.Gateway)
}
}
return nil
}
func validateIPAM(ipam *api.IPAMOptions, pg plugingetter.PluginGetter) error {
if ipam == nil {
// It is ok to not specify any IPAM configurations. We
// will choose good defaults.
return nil
}
if err := validateDriver(ipam.Driver, pg, ipamapi.PluginEndpointType); err != nil {
return err
}
for _, ipamConf := range ipam.Configs {
if err := validateIPAMConfiguration(ipamConf); err != nil {
return err
}
}
return nil
}
func validateNetworkSpec(spec *api.NetworkSpec, pg plugingetter.PluginGetter) error {
if spec == nil {
return status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
}
if spec.Ingress && spec.DriverConfig != nil && spec.DriverConfig.Name != "overlay" {
return status.Errorf(codes.Unimplemented, "only overlay driver is currently supported for ingress network")
}
if spec.Attachable && spec.Ingress {
return status.Errorf(codes.InvalidArgument, "ingress network cannot be attachable")
}
if err := validateAnnotations(spec.Annotations); err != nil {
return err
}
if _, ok := spec.Annotations.Labels[networkallocator.PredefinedLabel]; ok {
return status.Errorf(codes.PermissionDenied, "label %s is for internally created predefined networks and cannot be applied by users",
networkallocator.PredefinedLabel)
}
if err := validateDriver(spec.DriverConfig, pg, driverapi.NetworkPluginEndpointType); err != nil {
return err
}
return validateIPAM(spec.IPAM, pg)
}
// CreateNetwork creates and returns a Network based on the provided NetworkSpec.
// - Returns `InvalidArgument` if the NetworkSpec is malformed.
// - Returns an error if the creation fails.
func (s *Server) CreateNetwork(ctx context.Context, request *api.CreateNetworkRequest) (*api.CreateNetworkResponse, error) {
if err := validateNetworkSpec(request.Spec, s.pg); err != nil {
return nil, err
}
// TODO(mrjana): Consider using `Name` as a primary key to handle
// duplicate creations. See #65
n := &api.Network{
ID: identity.NewID(),
Spec: *request.Spec,
}
err := s.store.Update(func(tx store.Tx) error {
if request.Spec.Ingress {
if n, err := allocator.GetIngressNetwork(s.store); err == nil {
return status.Errorf(codes.AlreadyExists, "ingress network (%s) is already present", n.ID)
} else if err != allocator.ErrNoIngress {
return status.Errorf(codes.Internal, "failed ingress network presence check: %v", err)
}
}
return store.CreateNetwork(tx, n)
})
if err != nil {
return nil, err
}
return &api.CreateNetworkResponse{
Network: n,
}, nil
}
// GetNetwork returns a Network given a NetworkID.
// - Returns `InvalidArgument` if NetworkID is not provided.
// - Returns `NotFound` if the Network is not found.
func (s *Server) GetNetwork(ctx context.Context, request *api.GetNetworkRequest) (*api.GetNetworkResponse, error) {
if request.NetworkID == "" {
return nil, status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
}
var n *api.Network
s.store.View(func(tx store.ReadTx) {
n = store.GetNetwork(tx, request.NetworkID)
})
if n == nil {
return nil, status.Errorf(codes.NotFound, "network %s not found", request.NetworkID)
}
return &api.GetNetworkResponse{
Network: n,
}, nil
}
// RemoveNetwork removes a Network referenced by NetworkID.
// - Returns `InvalidArgument` if NetworkID is not provided.
// - Returns `NotFound` if the Network is not found.
// - Returns an error if the deletion fails.
func (s *Server) RemoveNetwork(ctx context.Context, request *api.RemoveNetworkRequest) (*api.RemoveNetworkResponse, error) {
if request.NetworkID == "" {
return nil, status.Errorf(codes.InvalidArgument, errInvalidArgument.Error())
}
var (
n *api.Network
rm = s.removeNetwork
)
s.store.View(func(tx store.ReadTx) {
n = store.GetNetwork(tx, request.NetworkID)
})
if n == nil {
return nil, status.Errorf(codes.NotFound, "network %s not found", request.NetworkID)
}
if allocator.IsIngressNetwork(n) {
rm = s.removeIngressNetwork
}
if v, ok := n.Spec.Annotations.Labels[networkallocator.PredefinedLabel]; ok && v == "true" {
return nil, status.Errorf(codes.FailedPrecondition, "network %s (%s) is a swarm predefined network and cannot be removed",
request.NetworkID, n.Spec.Annotations.Name)
}
if err := rm(n.ID); err != nil {
if err == store.ErrNotExist {
return nil, status.Errorf(codes.NotFound, "network %s not found", request.NetworkID)
}
return nil, err
}
return &api.RemoveNetworkResponse{}, nil
}
func (s *Server) removeNetwork(id string) error {
return s.store.Update(func(tx store.Tx) error {
services, err := store.FindServices(tx, store.ByReferencedNetworkID(id))
if err != nil {
return status.Errorf(codes.Internal, "could not find services using network %s: %v", id, err)
}
if len(services) != 0 {
return status.Errorf(codes.FailedPrecondition, "network %s is in use by service %s", id, services[0].ID)
}
tasks, err := store.FindTasks(tx, store.ByReferencedNetworkID(id))
if err != nil {
return status.Errorf(codes.Internal, "could not find tasks using network %s: %v", id, err)
}
for _, t := range tasks {
if t.DesiredState <= api.TaskStateRunning && t.Status.State <= api.TaskStateRunning {
return status.Errorf(codes.FailedPrecondition, "network %s is in use by task %s", id, t.ID)
}
}
return store.DeleteNetwork(tx, id)
})
}
func (s *Server) removeIngressNetwork(id string) error {
return s.store.Update(func(tx store.Tx) error {
services, err := store.FindServices(tx, store.All)
if err != nil {
return status.Errorf(codes.Internal, "could not find services using network %s: %v", id, err)
}
for _, srv := range services {
if allocator.IsIngressNetworkNeeded(srv) {
return status.Errorf(codes.FailedPrecondition, "ingress network cannot be removed because service %s depends on it", srv.ID)
}
}
return store.DeleteNetwork(tx, id)
})
}
func filterNetworks(candidates []*api.Network, filters ...func(*api.Network) bool) []*api.Network {
result := []*api.Network{}
for _, c := range candidates {
match := true
for _, f := range filters {
if !f(c) {
match = false
break
}
}
if match {
result = append(result, c)
}
}
return result
}
// ListNetworks returns a list of all networks.
func (s *Server) ListNetworks(ctx context.Context, request *api.ListNetworksRequest) (*api.ListNetworksResponse, error) {
var (
networks []*api.Network
err error
)
s.store.View(func(tx store.ReadTx) {
switch {
case request.Filters != nil && len(request.Filters.Names) > 0:
networks, err = store.FindNetworks(tx, buildFilters(store.ByName, request.Filters.Names))
case request.Filters != nil && len(request.Filters.NamePrefixes) > 0:
networks, err = store.FindNetworks(tx, buildFilters(store.ByNamePrefix, request.Filters.NamePrefixes))
case request.Filters != nil && len(request.Filters.IDPrefixes) > 0:
networks, err = store.FindNetworks(tx, buildFilters(store.ByIDPrefix, request.Filters.IDPrefixes))
default:
networks, err = store.FindNetworks(tx, store.All)
}
})
if err != nil {
return nil, err
}
if request.Filters != nil {
networks = filterNetworks(networks,
func(e *api.Network) bool {
return filterContains(e.Spec.Annotations.Name, request.Filters.Names)
},
func(e *api.Network) bool {
return filterContainsPrefix(e.Spec.Annotations.Name, request.Filters.NamePrefixes)
},
func(e *api.Network) bool {
return filterContainsPrefix(e.ID, request.Filters.IDPrefixes)
},
func(e *api.Network) bool {
return filterMatchLabels(e.Spec.Annotations.Labels, request.Filters.Labels)
},
)
}
return &api.ListNetworksResponse{
Networks: networks,
}, nil
}