moby/daemon/network.go

530 lines
15 KiB
Go
Raw Normal View History

package daemon
import (
"fmt"
"net"
"runtime"
"sort"
"strings"
"sync"
"github.com/Sirupsen/logrus"
apierrors "github.com/docker/docker/api/errors"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
clustertypes "github.com/docker/docker/daemon/cluster/provider"
"github.com/docker/docker/pkg/plugingetter"
"github.com/docker/docker/runconfig"
"github.com/docker/libnetwork"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/ipamapi"
networktypes "github.com/docker/libnetwork/types"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
// NetworkControllerEnabled checks if the networking stack is enabled.
// This feature depends on OS primitives and it's disabled in systems like Windows.
func (daemon *Daemon) NetworkControllerEnabled() bool {
return daemon.netController != nil
}
// FindNetwork function finds a network for a given string that can represent network name or id
func (daemon *Daemon) FindNetwork(idName string) (libnetwork.Network, error) {
// Find by Name
n, err := daemon.GetNetworkByName(idName)
if err != nil && !isNoSuchNetworkError(err) {
return nil, err
}
if n != nil {
return n, nil
}
// Find by id
return daemon.GetNetworkByID(idName)
}
func isNoSuchNetworkError(err error) bool {
_, ok := err.(libnetwork.ErrNoSuchNetwork)
return ok
}
// GetNetworkByID function returns a network whose ID begins with the given prefix.
// It fails with an error if no matching, or more than one matching, networks are found.
func (daemon *Daemon) GetNetworkByID(partialID string) (libnetwork.Network, error) {
list := daemon.GetNetworksByID(partialID)
if len(list) == 0 {
return nil, libnetwork.ErrNoSuchNetwork(partialID)
}
if len(list) > 1 {
return nil, libnetwork.ErrInvalidID(partialID)
}
return list[0], nil
}
// GetNetworkByName function returns a network for a given network name.
// If no network name is given, the default network is returned.
func (daemon *Daemon) GetNetworkByName(name string) (libnetwork.Network, error) {
c := daemon.netController
if c == nil {
return nil, libnetwork.ErrNoSuchNetwork(name)
}
if name == "" {
name = c.Config().Daemon.DefaultNetwork
}
return c.NetworkByName(name)
}
// GetNetworksByID returns a list of networks whose ID partially matches zero or more networks
func (daemon *Daemon) GetNetworksByID(partialID string) []libnetwork.Network {
c := daemon.netController
if c == nil {
return nil
}
list := []libnetwork.Network{}
l := func(nw libnetwork.Network) bool {
if strings.HasPrefix(nw.ID(), partialID) {
list = append(list, nw)
}
return false
}
c.WalkNetworks(l)
return list
}
// getAllNetworks returns a list containing all networks
func (daemon *Daemon) getAllNetworks() []libnetwork.Network {
return daemon.netController.Networks()
}
type ingressJob struct {
create *clustertypes.NetworkCreateRequest
ip net.IP
}
var (
ingressWorkerOnce sync.Once
ingressJobsChannel chan *ingressJob
ingressID string
)
func (daemon *Daemon) startIngressWorker() {
ingressJobsChannel = make(chan *ingressJob, 100)
go func() {
for {
select {
case r := <-ingressJobsChannel:
if r.create != nil {
daemon.setupIngress(r.create, r.ip, ingressID)
ingressID = r.create.ID
} else {
daemon.releaseIngress(ingressID)
ingressID = ""
}
}
}
}()
}
// enqueueIngressJob adds a ingress add/rm request to the worker queue.
// It guarantees the worker is started.
func (daemon *Daemon) enqueueIngressJob(job *ingressJob) {
ingressWorkerOnce.Do(daemon.startIngressWorker)
ingressJobsChannel <- job
}
// SetupIngress setups ingress networking.
func (daemon *Daemon) SetupIngress(create clustertypes.NetworkCreateRequest, nodeIP string) error {
ip, _, err := net.ParseCIDR(nodeIP)
if err != nil {
return err
}
daemon.enqueueIngressJob(&ingressJob{&create, ip})
return nil
}
// ReleaseIngress releases the ingress networking.
func (daemon *Daemon) ReleaseIngress() error {
daemon.enqueueIngressJob(&ingressJob{nil, nil})
return nil
}
func (daemon *Daemon) setupIngress(create *clustertypes.NetworkCreateRequest, ip net.IP, staleID string) {
controller := daemon.netController
controller.AgentInitWait()
if staleID != "" && staleID != create.ID {
daemon.releaseIngress(staleID)
}
if _, err := daemon.createNetwork(create.NetworkCreateRequest, create.ID, true); err != nil {
// If it is any other error other than already
// exists error log error and return.
if _, ok := err.(libnetwork.NetworkNameError); !ok {
logrus.Errorf("Failed creating ingress network: %v", err)
return
}
// Otherwise continue down the call to create or recreate sandbox.
}
n, err := daemon.GetNetworkByID(create.ID)
if err != nil {
logrus.Errorf("Failed getting ingress network by id after creating: %v", err)
}
sb, err := controller.NewSandbox("ingress-sbox", libnetwork.OptionIngress())
if err != nil {
if _, ok := err.(networktypes.ForbiddenError); !ok {
logrus.Errorf("Failed creating ingress sandbox: %v", err)
}
return
}
ep, err := n.CreateEndpoint("ingress-endpoint", libnetwork.CreateOptionIpam(ip, nil, nil, nil))
if err != nil {
logrus.Errorf("Failed creating ingress endpoint: %v", err)
return
}
if err := ep.Join(sb, nil); err != nil {
logrus.Errorf("Failed joining ingress sandbox to ingress endpoint: %v", err)
return
}
if err := sb.EnableService(); err != nil {
logrus.Errorf("Failed enabling service for ingress sandbox")
}
}
func (daemon *Daemon) releaseIngress(id string) {
controller := daemon.netController
if err := controller.SandboxDestroy("ingress-sbox"); err != nil {
logrus.Errorf("Failed to delete ingress sandbox: %v", err)
}
if id == "" {
return
}
n, err := controller.NetworkByID(id)
if err != nil {
logrus.Errorf("failed to retrieve ingress network %s: %v", id, err)
return
}
for _, ep := range n.Endpoints() {
if err := ep.Delete(true); err != nil {
logrus.Errorf("Failed to delete endpoint %s (%s): %v", ep.Name(), ep.ID(), err)
return
}
}
if err := n.Delete(); err != nil {
logrus.Errorf("Failed to delete ingress network %s: %v", n.ID(), err)
return
}
return
}
// SetNetworkBootstrapKeys sets the bootstrap keys.
func (daemon *Daemon) SetNetworkBootstrapKeys(keys []*networktypes.EncryptionKey) error {
return daemon.netController.SetKeys(keys)
}
// UpdateAttachment notifies the attacher about the attachment config.
func (daemon *Daemon) UpdateAttachment(networkName, networkID, containerID string, config *network.NetworkingConfig) error {
if daemon.clusterProvider == nil {
return fmt.Errorf("cluster provider is not initialized")
}
if err := daemon.clusterProvider.UpdateAttachment(networkName, containerID, config); err != nil {
return daemon.clusterProvider.UpdateAttachment(networkID, containerID, config)
}
return nil
}
// WaitForDetachment makes the cluster manager wait for detachment of
// the container from the network.
func (daemon *Daemon) WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error {
if daemon.clusterProvider == nil {
return fmt.Errorf("cluster provider is not initialized")
}
return daemon.clusterProvider.WaitForDetachment(ctx, networkName, networkID, taskID, containerID)
}
// CreateManagedNetwork creates an agent network.
func (daemon *Daemon) CreateManagedNetwork(create clustertypes.NetworkCreateRequest) error {
_, err := daemon.createNetwork(create.NetworkCreateRequest, create.ID, true)
return err
}
// CreateNetwork creates a network with the given name, driver and other optional parameters
func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.NetworkCreateResponse, error) {
resp, err := daemon.createNetwork(create, "", false)
if err != nil {
return nil, err
}
return resp, err
}
func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string, agent bool) (*types.NetworkCreateResponse, error) {
if runconfig.IsPreDefinedNetwork(create.Name) && !agent {
err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name)
return nil, apierrors.NewRequestForbiddenError(err)
}
var warning string
nw, err := daemon.GetNetworkByName(create.Name)
if err != nil {
if _, ok := err.(libnetwork.ErrNoSuchNetwork); !ok {
return nil, err
}
}
if nw != nil {
// check if user defined CheckDuplicate, if set true, return err
// otherwise prepare a warning message
if create.CheckDuplicate {
return nil, libnetwork.NetworkNameError(create.Name)
}
warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
}
c := daemon.netController
driver := create.Driver
if driver == "" {
driver = c.Config().Daemon.DefaultDriver
}
nwOptions := []libnetwork.NetworkOption{
libnetwork.NetworkOptionEnableIPv6(create.EnableIPv6),
libnetwork.NetworkOptionDriverOpts(create.Options),
libnetwork.NetworkOptionLabels(create.Labels),
libnetwork.NetworkOptionAttachable(create.Attachable),
libnetwork.NetworkOptionIngress(create.Ingress),
}
if create.IPAM != nil {
ipam := create.IPAM
v4Conf, v6Conf, err := getIpamConfig(ipam.Config)
if err != nil {
return nil, err
}
nwOptions = append(nwOptions, libnetwork.NetworkOptionIpam(ipam.Driver, "", v4Conf, v6Conf, ipam.Options))
}
if create.Internal {
nwOptions = append(nwOptions, libnetwork.NetworkOptionInternalNetwork())
}
if agent {
nwOptions = append(nwOptions, libnetwork.NetworkOptionDynamic())
nwOptions = append(nwOptions, libnetwork.NetworkOptionPersist(false))
}
n, err := c.NewNetwork(driver, create.Name, id, nwOptions...)
if err != nil {
return nil, err
}
daemon.pluginRefCount(driver, driverapi.NetworkPluginEndpointType, plugingetter.Acquire)
if create.IPAM != nil {
daemon.pluginRefCount(create.IPAM.Driver, ipamapi.PluginEndpointType, plugingetter.Acquire)
}
daemon.LogNetworkEvent(n, "create")
return &types.NetworkCreateResponse{
ID: n.ID(),
Warning: warning,
}, nil
}
func (daemon *Daemon) pluginRefCount(driver, capability string, mode int) {
var builtinDrivers []string
if capability == driverapi.NetworkPluginEndpointType {
builtinDrivers = daemon.netController.BuiltinDrivers()
} else if capability == ipamapi.PluginEndpointType {
builtinDrivers = daemon.netController.BuiltinIPAMDrivers()
}
for _, d := range builtinDrivers {
if d == driver {
return
}
}
if daemon.PluginStore != nil {
_, err := daemon.PluginStore.Get(driver, capability, mode)
if err != nil {
logrus.WithError(err).WithFields(logrus.Fields{"mode": mode, "driver": driver}).Error("Error handling plugin refcount operation")
}
}
}
func getIpamConfig(data []network.IPAMConfig) ([]*libnetwork.IpamConf, []*libnetwork.IpamConf, error) {
ipamV4Cfg := []*libnetwork.IpamConf{}
ipamV6Cfg := []*libnetwork.IpamConf{}
for _, d := range data {
iCfg := libnetwork.IpamConf{}
iCfg.PreferredPool = d.Subnet
iCfg.SubPool = d.IPRange
iCfg.Gateway = d.Gateway
iCfg.AuxAddresses = d.AuxAddress
ip, _, err := net.ParseCIDR(d.Subnet)
if err != nil {
return nil, nil, fmt.Errorf("Invalid subnet %s : %v", d.Subnet, err)
}
if ip.To4() != nil {
ipamV4Cfg = append(ipamV4Cfg, &iCfg)
} else {
ipamV6Cfg = append(ipamV6Cfg, &iCfg)
}
}
return ipamV4Cfg, ipamV6Cfg, nil
}
// UpdateContainerServiceConfig updates a service configuration.
func (daemon *Daemon) UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error {
container, err := daemon.GetContainer(containerName)
if err != nil {
return err
}
container.NetworkSettings.Service = serviceConfig
return nil
}
// ConnectContainerToNetwork connects the given container to the given
// network. If either cannot be found, an err is returned. If the
// network cannot be set up, an err is returned.
func (daemon *Daemon) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error {
if runtime.GOOS == "solaris" {
return errors.New("docker network connect is unsupported on Solaris platform")
}
container, err := daemon.GetContainer(containerName)
if err != nil {
return err
}
return daemon.ConnectToNetwork(container, networkName, endpointConfig)
}
// DisconnectContainerFromNetwork disconnects the given container from
// the given network. If either cannot be found, an err is returned.
func (daemon *Daemon) DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error {
if runtime.GOOS == "solaris" {
return errors.New("docker network disconnect is unsupported on Solaris platform")
}
container, err := daemon.GetContainer(containerName)
if err != nil {
if force {
return daemon.ForceEndpointDelete(containerName, networkName)
}
return err
}
return daemon.DisconnectFromNetwork(container, networkName, force)
}
// GetNetworkDriverList returns the list of plugins drivers
// registered for network.
func (daemon *Daemon) GetNetworkDriverList() []string {
if !daemon.NetworkControllerEnabled() {
return nil
}
pluginList := daemon.netController.BuiltinDrivers()
managedPlugins := daemon.PluginStore.GetAllManagedPluginsByCap(driverapi.NetworkPluginEndpointType)
for _, plugin := range managedPlugins {
pluginList = append(pluginList, plugin.Name())
}
pluginMap := make(map[string]bool)
for _, plugin := range pluginList {
pluginMap[plugin] = true
}
networks := daemon.netController.Networks()
for _, network := range networks {
if !pluginMap[network.Type()] {
pluginList = append(pluginList, network.Type())
pluginMap[network.Type()] = true
}
}
sort.Strings(pluginList)
return pluginList
}
// DeleteManagedNetwork deletes an agent network.
func (daemon *Daemon) DeleteManagedNetwork(networkID string) error {
return daemon.deleteNetwork(networkID, true)
}
// DeleteNetwork destroys a network unless it's one of docker's predefined networks.
func (daemon *Daemon) DeleteNetwork(networkID string) error {
return daemon.deleteNetwork(networkID, false)
}
func (daemon *Daemon) deleteNetwork(networkID string, dynamic bool) error {
nw, err := daemon.FindNetwork(networkID)
if err != nil {
return err
}
if runconfig.IsPreDefinedNetwork(nw.Name()) && !dynamic {
Remove static errors from errors package. Moving all strings to the errors package wasn't a good idea after all. Our custom implementation of Go errors predates everything that's nice and good about working with errors in Go. Take as an example what we have to do to get an error message: ```go func GetErrorMessage(err error) string { switch err.(type) { case errcode.Error: e, _ := err.(errcode.Error) return e.Message case errcode.ErrorCode: ec, _ := err.(errcode.ErrorCode) return ec.Message() default: return err.Error() } } ``` This goes against every good practice for Go development. The language already provides a simple, intuitive and standard way to get error messages, that is calling the `Error()` method from an error. Reinventing the error interface is a mistake. Our custom implementation also makes very hard to reason about errors, another nice thing about Go. I found several (>10) error declarations that we don't use anywhere. This is a clear sign about how little we know about the errors we return. I also found several error usages where the number of arguments was different than the parameters declared in the error, another clear example of how difficult is to reason about errors. Moreover, our custom implementation didn't really make easier for people to return custom HTTP status code depending on the errors. Again, it's hard to reason about when to set custom codes and how. Take an example what we have to do to extract the message and status code from an error before returning a response from the API: ```go switch err.(type) { case errcode.ErrorCode: daError, _ := err.(errcode.ErrorCode) statusCode = daError.Descriptor().HTTPStatusCode errMsg = daError.Message() case errcode.Error: // For reference, if you're looking for a particular error // then you can do something like : // import ( derr "github.com/docker/docker/errors" ) // if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... } daError, _ := err.(errcode.Error) statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode errMsg = daError.Message default: // This part of will be removed once we've // converted everything over to use the errcode package // FIXME: this is brittle and should not be necessary. // If we need to differentiate between different possible error types, // we should create appropriate error types with clearly defined meaning errStr := strings.ToLower(err.Error()) for keyword, status := range map[string]int{ "not found": http.StatusNotFound, "no such": http.StatusNotFound, "bad parameter": http.StatusBadRequest, "conflict": http.StatusConflict, "impossible": http.StatusNotAcceptable, "wrong login/password": http.StatusUnauthorized, "hasn't been activated": http.StatusForbidden, } { if strings.Contains(errStr, keyword) { statusCode = status break } } } ``` You can notice two things in that code: 1. We have to explain how errors work, because our implementation goes against how easy to use Go errors are. 2. At no moment we arrived to remove that `switch` statement that was the original reason to use our custom implementation. This change removes all our status errors from the errors package and puts them back in their specific contexts. IT puts the messages back with their contexts. That way, we know right away when errors used and how to generate their messages. It uses custom interfaces to reason about errors. Errors that need to response with a custom status code MUST implementent this simple interface: ```go type errorWithStatus interface { HTTPErrorStatusCode() int } ``` This interface is very straightforward to implement. It also preserves Go errors real behavior, getting the message is as simple as using the `Error()` method. I included helper functions to generate errors that use custom status code in `errors/errors.go`. By doing this, we remove the hard dependency we have eeverywhere to our custom errors package. Yes, you can use it as a helper to generate error, but it's still very easy to generate errors without it. Please, read this fantastic blog post about errors in Go: http://dave.cheney.net/2014/12/24/inspecting-errors Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-02-25 15:53:35 +00:00
err := fmt.Errorf("%s is a pre-defined network and cannot be removed", nw.Name())
return apierrors.NewRequestForbiddenError(err)
}
if err := nw.Delete(); err != nil {
return err
}
daemon.pluginRefCount(nw.Type(), driverapi.NetworkPluginEndpointType, plugingetter.Release)
ipamType, _, _, _ := nw.Info().IpamConfig()
daemon.pluginRefCount(ipamType, ipamapi.PluginEndpointType, plugingetter.Release)
daemon.LogNetworkEvent(nw, "destroy")
return nil
}
// GetNetworks returns a list of all networks
func (daemon *Daemon) GetNetworks() []libnetwork.Network {
return daemon.getAllNetworks()
}
// clearAttachableNetworks removes the attachable networks
// after disconnecting any connected container
func (daemon *Daemon) clearAttachableNetworks() {
for _, n := range daemon.GetNetworks() {
if !n.Info().Attachable() {
continue
}
for _, ep := range n.Endpoints() {
epInfo := ep.Info()
if epInfo == nil {
continue
}
sb := epInfo.Sandbox()
if sb == nil {
continue
}
containerID := sb.ContainerID()
if err := daemon.DisconnectContainerFromNetwork(containerID, n.ID(), true); err != nil {
logrus.Warnf("Failed to disconnect container %s from swarm network %s on cluster leave: %v",
containerID, n.Name(), err)
}
}
if err := daemon.DeleteManagedNetwork(n.ID()); err != nil {
logrus.Warnf("Failed to remove swarm network %s on cluster leave: %v", n.Name(), err)
}
}
}