| // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: | |
| //go:build go1.19 | |
| package libnetwork | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "net" | |
| "runtime" | |
| "strings" | |
| "sync" | |
| "time" | |
| "github.com/docker/docker/libnetwork/config" | |
| "github.com/docker/docker/libnetwork/datastore" | |
| "github.com/docker/docker/libnetwork/driverapi" | |
| "github.com/docker/docker/libnetwork/etchosts" | |
| "github.com/docker/docker/libnetwork/internal/setmatrix" | |
| "github.com/docker/docker/libnetwork/ipamapi" | |
| "github.com/docker/docker/libnetwork/netlabel" | |
| "github.com/docker/docker/libnetwork/netutils" | |
| "github.com/docker/docker/libnetwork/networkdb" | |
| "github.com/docker/docker/libnetwork/options" | |
| "github.com/docker/docker/libnetwork/types" | |
| "github.com/docker/docker/pkg/stringid" | |
| "github.com/sirupsen/logrus" | |
| ) | |
| // A Network represents a logical connectivity zone that containers may | |
| // join using the Link method. A Network is managed by a specific driver. | |
| type Network interface { | |
| // Name returns a user chosen name for this network. | |
| Name() string | |
| // ID returns a system generated id for this network. | |
| ID() string | |
| // Type returns the type of network, which corresponds to its managing driver. | |
| Type() string | |
| // CreateEndpoint creates a new endpoint to this network symbolically identified by the | |
| // specified unique name. The options parameter carries driver specific options. | |
| CreateEndpoint(name string, options ...EndpointOption) (*Endpoint, error) | |
| // Delete the network. | |
| Delete(options ...NetworkDeleteOption) error | |
| // Endpoints returns the list of Endpoint(s) in this network. | |
| Endpoints() []*Endpoint | |
| // WalkEndpoints uses the provided function to walk the Endpoints. | |
| WalkEndpoints(walker EndpointWalker) | |
| // EndpointByName returns the Endpoint which has the passed name. If not found, the error ErrNoSuchEndpoint is returned. | |
| EndpointByName(name string) (*Endpoint, error) | |
| // EndpointByID returns the Endpoint which has the passed id. If not found, the error ErrNoSuchEndpoint is returned. | |
| EndpointByID(id string) (*Endpoint, error) | |
| // Info returns certain operational data belonging to this network. | |
| Info() NetworkInfo | |
| } | |
| // NetworkInfo returns some configuration and operational information about the network | |
| type NetworkInfo interface { | |
| IpamConfig() (string, map[string]string, []*IpamConf, []*IpamConf) | |
| IpamInfo() ([]*IpamInfo, []*IpamInfo) | |
| DriverOptions() map[string]string | |
| Scope() string | |
| IPv6Enabled() bool | |
| Internal() bool | |
| Attachable() bool | |
| Ingress() bool | |
| ConfigFrom() string | |
| ConfigOnly() bool | |
| Labels() map[string]string | |
| Dynamic() bool | |
| Created() time.Time | |
| // Peers returns a slice of PeerInfo structures which has the information about the peer | |
| // nodes participating in the same overlay network. This is currently the per-network | |
| // gossip cluster. For non-dynamic overlay networks and bridge networks it returns an | |
| // empty slice | |
| Peers() []networkdb.PeerInfo | |
| // Services returns a map of services keyed by the service name with the details | |
| // of all the tasks that belong to the service. Applicable only in swarm mode. | |
| Services() map[string]ServiceInfo | |
| } | |
| // EndpointWalker is a client provided function which will be used to walk the Endpoints. | |
| // When the function returns true, the walk will stop. | |
| type EndpointWalker func(ep *Endpoint) bool | |
| // ipInfo is the reverse mapping from IP to service name to serve the PTR query. | |
| // extResolver is set if an external server resolves a service name to this IP. | |
| // It's an indication to defer PTR queries also to that external server. | |
| type ipInfo struct { | |
| name string | |
| serviceID string | |
| extResolver bool | |
| } | |
| // svcMapEntry is the body of the element into the svcMap | |
| // The ip is a string because the SetMatrix does not accept non hashable values | |
| type svcMapEntry struct { | |
| ip string | |
| serviceID string | |
| } | |
| type svcInfo struct { | |
| svcMap setmatrix.SetMatrix[svcMapEntry] | |
| svcIPv6Map setmatrix.SetMatrix[svcMapEntry] | |
| ipMap setmatrix.SetMatrix[ipInfo] | |
| service map[string][]servicePorts | |
| } | |
| // backing container or host's info | |
| type serviceTarget struct { | |
| name string | |
| ip net.IP | |
| port uint16 | |
| } | |
| type servicePorts struct { | |
| portName string | |
| proto string | |
| target []serviceTarget | |
| } | |
| type networkDBTable struct { | |
| name string | |
| objType driverapi.ObjectType | |
| } | |
| // IpamConf contains all the ipam related configurations for a network | |
| type IpamConf struct { | |
| // PreferredPool is the master address pool for containers and network interfaces. | |
| PreferredPool string | |
| // SubPool is a subset of the master pool. If specified, | |
| // this becomes the container pool. | |
| SubPool string | |
| // Gateway is the preferred Network Gateway address (optional). | |
| Gateway string | |
| // AuxAddresses contains auxiliary addresses for network driver. Must be within the master pool. | |
| // libnetwork will reserve them if they fall into the container pool. | |
| AuxAddresses map[string]string | |
| } | |
| // Validate checks whether the configuration is valid | |
| func (c *IpamConf) Validate() error { | |
| if c.Gateway != "" && nil == net.ParseIP(c.Gateway) { | |
| return types.BadRequestErrorf("invalid gateway address %s in Ipam configuration", c.Gateway) | |
| } | |
| return nil | |
| } | |
| // IpamInfo contains all the ipam related operational info for a network | |
| type IpamInfo struct { | |
| PoolID string | |
| Meta map[string]string | |
| driverapi.IPAMData | |
| } | |
| // MarshalJSON encodes IpamInfo into json message | |
| func (i *IpamInfo) MarshalJSON() ([]byte, error) { | |
| m := map[string]interface{}{ | |
| "PoolID": i.PoolID, | |
| } | |
| v, err := json.Marshal(&i.IPAMData) | |
| if err != nil { | |
| return nil, err | |
| } | |
| m["IPAMData"] = string(v) | |
| if i.Meta != nil { | |
| m["Meta"] = i.Meta | |
| } | |
| return json.Marshal(m) | |
| } | |
| // UnmarshalJSON decodes json message into PoolData | |
| func (i *IpamInfo) UnmarshalJSON(data []byte) error { | |
| var ( | |
| m map[string]interface{} | |
| err error | |
| ) | |
| if err = json.Unmarshal(data, &m); err != nil { | |
| return err | |
| } | |
| i.PoolID = m["PoolID"].(string) | |
| if v, ok := m["Meta"]; ok { | |
| b, _ := json.Marshal(v) | |
| if err = json.Unmarshal(b, &i.Meta); err != nil { | |
| return err | |
| } | |
| } | |
| if v, ok := m["IPAMData"]; ok { | |
| if err = json.Unmarshal([]byte(v.(string)), &i.IPAMData); err != nil { | |
| return err | |
| } | |
| } | |
| return nil | |
| } | |
| type network struct { | |
| ctrlr *Controller | |
| name string | |
| networkType string | |
| id string | |
| created time.Time | |
| scope string // network data scope | |
| labels map[string]string | |
| ipamType string | |
| ipamOptions map[string]string | |
| addrSpace string | |
| ipamV4Config []*IpamConf | |
| ipamV6Config []*IpamConf | |
| ipamV4Info []*IpamInfo | |
| ipamV6Info []*IpamInfo | |
| enableIPv6 bool | |
| postIPv6 bool | |
| epCnt *endpointCnt | |
| generic options.Generic | |
| dbIndex uint64 | |
| dbExists bool | |
| persist bool | |
| drvOnce *sync.Once | |
| resolverOnce sync.Once //nolint:nolintlint,unused // only used on windows | |
| resolver []*Resolver | |
| internal bool | |
| attachable bool | |
| inDelete bool | |
| ingress bool | |
| driverTables []networkDBTable | |
| dynamic bool | |
| configOnly bool | |
| configFrom string | |
| loadBalancerIP net.IP | |
| loadBalancerMode string | |
| mu sync.Mutex | |
| } | |
| const ( | |
| loadBalancerModeNAT = "NAT" | |
| loadBalancerModeDSR = "DSR" | |
| loadBalancerModeDefault = loadBalancerModeNAT | |
| ) | |
| func (n *network) Name() string { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.name | |
| } | |
| func (n *network) ID() string { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.id | |
| } | |
| func (n *network) Created() time.Time { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.created | |
| } | |
| func (n *network) Type() string { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.networkType | |
| } | |
| func (n *network) Key() []string { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return []string{datastore.NetworkKeyPrefix, n.id} | |
| } | |
| func (n *network) KeyPrefix() []string { | |
| return []string{datastore.NetworkKeyPrefix} | |
| } | |
| func (n *network) Value() []byte { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| b, err := json.Marshal(n) | |
| if err != nil { | |
| return nil | |
| } | |
| return b | |
| } | |
| func (n *network) SetValue(value []byte) error { | |
| return json.Unmarshal(value, n) | |
| } | |
| func (n *network) Index() uint64 { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.dbIndex | |
| } | |
| func (n *network) SetIndex(index uint64) { | |
| n.mu.Lock() | |
| n.dbIndex = index | |
| n.dbExists = true | |
| n.mu.Unlock() | |
| } | |
| func (n *network) Exists() bool { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.dbExists | |
| } | |
| func (n *network) Skip() bool { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return !n.persist | |
| } | |
| func (n *network) New() datastore.KVObject { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return &network{ | |
| ctrlr: n.ctrlr, | |
| drvOnce: &sync.Once{}, | |
| scope: n.scope, | |
| } | |
| } | |
| // CopyTo deep copies to the destination IpamConfig | |
| func (c *IpamConf) CopyTo(dstC *IpamConf) error { | |
| dstC.PreferredPool = c.PreferredPool | |
| dstC.SubPool = c.SubPool | |
| dstC.Gateway = c.Gateway | |
| if c.AuxAddresses != nil { | |
| dstC.AuxAddresses = make(map[string]string, len(c.AuxAddresses)) | |
| for k, v := range c.AuxAddresses { | |
| dstC.AuxAddresses[k] = v | |
| } | |
| } | |
| return nil | |
| } | |
| // CopyTo deep copies to the destination IpamInfo | |
| func (i *IpamInfo) CopyTo(dstI *IpamInfo) error { | |
| dstI.PoolID = i.PoolID | |
| if i.Meta != nil { | |
| dstI.Meta = make(map[string]string) | |
| for k, v := range i.Meta { | |
| dstI.Meta[k] = v | |
| } | |
| } | |
| dstI.AddressSpace = i.AddressSpace | |
| dstI.Pool = types.GetIPNetCopy(i.Pool) | |
| dstI.Gateway = types.GetIPNetCopy(i.Gateway) | |
| if i.AuxAddresses != nil { | |
| dstI.AuxAddresses = make(map[string]*net.IPNet) | |
| for k, v := range i.AuxAddresses { | |
| dstI.AuxAddresses[k] = types.GetIPNetCopy(v) | |
| } | |
| } | |
| return nil | |
| } | |
| func (n *network) validateConfiguration() error { | |
| if n.configOnly { | |
| // Only supports network specific configurations. | |
| // Network operator configurations are not supported. | |
| if n.ingress || n.internal || n.attachable || n.scope != "" { | |
| return types.ForbiddenErrorf("configuration network can only contain network " + | |
| "specific fields. Network operator fields like " + | |
| "[ ingress | internal | attachable | scope ] are not supported.") | |
| } | |
| } | |
| if n.configFrom != "" { | |
| if n.configOnly { | |
| return types.ForbiddenErrorf("a configuration network cannot depend on another configuration network") | |
| } | |
| if n.ipamType != "" && | |
| n.ipamType != defaultIpamForNetworkType(n.networkType) || | |
| n.enableIPv6 || | |
| len(n.labels) > 0 || len(n.ipamOptions) > 0 || | |
| len(n.ipamV4Config) > 0 || len(n.ipamV6Config) > 0 { | |
| return types.ForbiddenErrorf("user specified configurations are not supported if the network depends on a configuration network") | |
| } | |
| if len(n.generic) > 0 { | |
| if data, ok := n.generic[netlabel.GenericData]; ok { | |
| var ( | |
| driverOptions map[string]string | |
| opts interface{} | |
| ) | |
| switch t := data.(type) { | |
| case map[string]interface{}, map[string]string: | |
| opts = t | |
| } | |
| ba, err := json.Marshal(opts) | |
| if err != nil { | |
| return fmt.Errorf("failed to validate network configuration: %v", err) | |
| } | |
| if err := json.Unmarshal(ba, &driverOptions); err != nil { | |
| return fmt.Errorf("failed to validate network configuration: %v", err) | |
| } | |
| if len(driverOptions) > 0 { | |
| return types.ForbiddenErrorf("network driver options are not supported if the network depends on a configuration network") | |
| } | |
| } | |
| } | |
| } | |
| return nil | |
| } | |
| // applyConfigurationTo applies network specific configurations. | |
| func (n *network) applyConfigurationTo(to *network) error { | |
| to.enableIPv6 = n.enableIPv6 | |
| if len(n.labels) > 0 { | |
| to.labels = make(map[string]string, len(n.labels)) | |
| for k, v := range n.labels { | |
| if _, ok := to.labels[k]; !ok { | |
| to.labels[k] = v | |
| } | |
| } | |
| } | |
| if len(n.ipamType) != 0 { | |
| to.ipamType = n.ipamType | |
| } | |
| if len(n.ipamOptions) > 0 { | |
| to.ipamOptions = make(map[string]string, len(n.ipamOptions)) | |
| for k, v := range n.ipamOptions { | |
| if _, ok := to.ipamOptions[k]; !ok { | |
| to.ipamOptions[k] = v | |
| } | |
| } | |
| } | |
| if len(n.ipamV4Config) > 0 { | |
| to.ipamV4Config = make([]*IpamConf, 0, len(n.ipamV4Config)) | |
| to.ipamV4Config = append(to.ipamV4Config, n.ipamV4Config...) | |
| } | |
| if len(n.ipamV6Config) > 0 { | |
| to.ipamV6Config = make([]*IpamConf, 0, len(n.ipamV6Config)) | |
| to.ipamV6Config = append(to.ipamV6Config, n.ipamV6Config...) | |
| } | |
| if len(n.generic) > 0 { | |
| to.generic = options.Generic{} | |
| for k, v := range n.generic { | |
| to.generic[k] = v | |
| } | |
| } | |
| return nil | |
| } | |
| func (n *network) CopyTo(o datastore.KVObject) error { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| dstN := o.(*network) | |
| dstN.name = n.name | |
| dstN.id = n.id | |
| dstN.created = n.created | |
| dstN.networkType = n.networkType | |
| dstN.scope = n.scope | |
| dstN.dynamic = n.dynamic | |
| dstN.ipamType = n.ipamType | |
| dstN.enableIPv6 = n.enableIPv6 | |
| dstN.persist = n.persist | |
| dstN.postIPv6 = n.postIPv6 | |
| dstN.dbIndex = n.dbIndex | |
| dstN.dbExists = n.dbExists | |
| dstN.drvOnce = n.drvOnce | |
| dstN.internal = n.internal | |
| dstN.attachable = n.attachable | |
| dstN.inDelete = n.inDelete | |
| dstN.ingress = n.ingress | |
| dstN.configOnly = n.configOnly | |
| dstN.configFrom = n.configFrom | |
| dstN.loadBalancerIP = n.loadBalancerIP | |
| dstN.loadBalancerMode = n.loadBalancerMode | |
| // copy labels | |
| if dstN.labels == nil { | |
| dstN.labels = make(map[string]string, len(n.labels)) | |
| } | |
| for k, v := range n.labels { | |
| dstN.labels[k] = v | |
| } | |
| if n.ipamOptions != nil { | |
| dstN.ipamOptions = make(map[string]string, len(n.ipamOptions)) | |
| for k, v := range n.ipamOptions { | |
| dstN.ipamOptions[k] = v | |
| } | |
| } | |
| for _, v4conf := range n.ipamV4Config { | |
| dstV4Conf := &IpamConf{} | |
| if err := v4conf.CopyTo(dstV4Conf); err != nil { | |
| return err | |
| } | |
| dstN.ipamV4Config = append(dstN.ipamV4Config, dstV4Conf) | |
| } | |
| for _, v4info := range n.ipamV4Info { | |
| dstV4Info := &IpamInfo{} | |
| if err := v4info.CopyTo(dstV4Info); err != nil { | |
| return err | |
| } | |
| dstN.ipamV4Info = append(dstN.ipamV4Info, dstV4Info) | |
| } | |
| for _, v6conf := range n.ipamV6Config { | |
| dstV6Conf := &IpamConf{} | |
| if err := v6conf.CopyTo(dstV6Conf); err != nil { | |
| return err | |
| } | |
| dstN.ipamV6Config = append(dstN.ipamV6Config, dstV6Conf) | |
| } | |
| for _, v6info := range n.ipamV6Info { | |
| dstV6Info := &IpamInfo{} | |
| if err := v6info.CopyTo(dstV6Info); err != nil { | |
| return err | |
| } | |
| dstN.ipamV6Info = append(dstN.ipamV6Info, dstV6Info) | |
| } | |
| dstN.generic = options.Generic{} | |
| for k, v := range n.generic { | |
| dstN.generic[k] = v | |
| } | |
| return nil | |
| } | |
| func (n *network) DataScope() string { | |
| s := n.Scope() | |
| // All swarm scope networks have local datascope | |
| if s == datastore.SwarmScope { | |
| s = datastore.LocalScope | |
| } | |
| return s | |
| } | |
| func (n *network) getEpCnt() *endpointCnt { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.epCnt | |
| } | |
| // TODO : Can be made much more generic with the help of reflection (but has some golang limitations) | |
| func (n *network) MarshalJSON() ([]byte, error) { | |
| netMap := make(map[string]interface{}) | |
| netMap["name"] = n.name | |
| netMap["id"] = n.id | |
| netMap["created"] = n.created | |
| netMap["networkType"] = n.networkType | |
| netMap["scope"] = n.scope | |
| netMap["labels"] = n.labels | |
| netMap["ipamType"] = n.ipamType | |
| netMap["ipamOptions"] = n.ipamOptions | |
| netMap["addrSpace"] = n.addrSpace | |
| netMap["enableIPv6"] = n.enableIPv6 | |
| if n.generic != nil { | |
| netMap["generic"] = n.generic | |
| } | |
| netMap["persist"] = n.persist | |
| netMap["postIPv6"] = n.postIPv6 | |
| if len(n.ipamV4Config) > 0 { | |
| ics, err := json.Marshal(n.ipamV4Config) | |
| if err != nil { | |
| return nil, err | |
| } | |
| netMap["ipamV4Config"] = string(ics) | |
| } | |
| if len(n.ipamV4Info) > 0 { | |
| iis, err := json.Marshal(n.ipamV4Info) | |
| if err != nil { | |
| return nil, err | |
| } | |
| netMap["ipamV4Info"] = string(iis) | |
| } | |
| if len(n.ipamV6Config) > 0 { | |
| ics, err := json.Marshal(n.ipamV6Config) | |
| if err != nil { | |
| return nil, err | |
| } | |
| netMap["ipamV6Config"] = string(ics) | |
| } | |
| if len(n.ipamV6Info) > 0 { | |
| iis, err := json.Marshal(n.ipamV6Info) | |
| if err != nil { | |
| return nil, err | |
| } | |
| netMap["ipamV6Info"] = string(iis) | |
| } | |
| netMap["internal"] = n.internal | |
| netMap["attachable"] = n.attachable | |
| netMap["inDelete"] = n.inDelete | |
| netMap["ingress"] = n.ingress | |
| netMap["configOnly"] = n.configOnly | |
| netMap["configFrom"] = n.configFrom | |
| netMap["loadBalancerIP"] = n.loadBalancerIP | |
| netMap["loadBalancerMode"] = n.loadBalancerMode | |
| return json.Marshal(netMap) | |
| } | |
| // TODO : Can be made much more generic with the help of reflection (but has some golang limitations) | |
| func (n *network) UnmarshalJSON(b []byte) (err error) { | |
| var netMap map[string]interface{} | |
| if err := json.Unmarshal(b, &netMap); err != nil { | |
| return err | |
| } | |
| n.name = netMap["name"].(string) | |
| n.id = netMap["id"].(string) | |
| // "created" is not available in older versions | |
| if v, ok := netMap["created"]; ok { | |
| // n.created is time.Time but marshalled as string | |
| if err = n.created.UnmarshalText([]byte(v.(string))); err != nil { | |
| logrus.Warnf("failed to unmarshal creation time %v: %v", v, err) | |
| n.created = time.Time{} | |
| } | |
| } | |
| n.networkType = netMap["networkType"].(string) | |
| n.enableIPv6 = netMap["enableIPv6"].(bool) | |
| // if we weren't unmarshaling to netMap we could simply set n.labels | |
| // unfortunately, we can't because map[string]interface{} != map[string]string | |
| if labels, ok := netMap["labels"].(map[string]interface{}); ok { | |
| n.labels = make(map[string]string, len(labels)) | |
| for label, value := range labels { | |
| n.labels[label] = value.(string) | |
| } | |
| } | |
| if v, ok := netMap["ipamOptions"]; ok { | |
| if iOpts, ok := v.(map[string]interface{}); ok { | |
| n.ipamOptions = make(map[string]string, len(iOpts)) | |
| for k, v := range iOpts { | |
| n.ipamOptions[k] = v.(string) | |
| } | |
| } | |
| } | |
| if v, ok := netMap["generic"]; ok { | |
| n.generic = v.(map[string]interface{}) | |
| // Restore opts in their map[string]string form | |
| if v, ok := n.generic[netlabel.GenericData]; ok { | |
| var lmap map[string]string | |
| ba, err := json.Marshal(v) | |
| if err != nil { | |
| return err | |
| } | |
| if err := json.Unmarshal(ba, &lmap); err != nil { | |
| return err | |
| } | |
| n.generic[netlabel.GenericData] = lmap | |
| } | |
| } | |
| if v, ok := netMap["persist"]; ok { | |
| n.persist = v.(bool) | |
| } | |
| if v, ok := netMap["postIPv6"]; ok { | |
| n.postIPv6 = v.(bool) | |
| } | |
| if v, ok := netMap["ipamType"]; ok { | |
| n.ipamType = v.(string) | |
| } else { | |
| n.ipamType = ipamapi.DefaultIPAM | |
| } | |
| if v, ok := netMap["addrSpace"]; ok { | |
| n.addrSpace = v.(string) | |
| } | |
| if v, ok := netMap["ipamV4Config"]; ok { | |
| if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Config); err != nil { | |
| return err | |
| } | |
| } | |
| if v, ok := netMap["ipamV4Info"]; ok { | |
| if err := json.Unmarshal([]byte(v.(string)), &n.ipamV4Info); err != nil { | |
| return err | |
| } | |
| } | |
| if v, ok := netMap["ipamV6Config"]; ok { | |
| if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Config); err != nil { | |
| return err | |
| } | |
| } | |
| if v, ok := netMap["ipamV6Info"]; ok { | |
| if err := json.Unmarshal([]byte(v.(string)), &n.ipamV6Info); err != nil { | |
| return err | |
| } | |
| } | |
| if v, ok := netMap["internal"]; ok { | |
| n.internal = v.(bool) | |
| } | |
| if v, ok := netMap["attachable"]; ok { | |
| n.attachable = v.(bool) | |
| } | |
| if s, ok := netMap["scope"]; ok { | |
| n.scope = s.(string) | |
| } | |
| if v, ok := netMap["inDelete"]; ok { | |
| n.inDelete = v.(bool) | |
| } | |
| if v, ok := netMap["ingress"]; ok { | |
| n.ingress = v.(bool) | |
| } | |
| if v, ok := netMap["configOnly"]; ok { | |
| n.configOnly = v.(bool) | |
| } | |
| if v, ok := netMap["configFrom"]; ok { | |
| n.configFrom = v.(string) | |
| } | |
| if v, ok := netMap["loadBalancerIP"]; ok { | |
| n.loadBalancerIP = net.ParseIP(v.(string)) | |
| } | |
| n.loadBalancerMode = loadBalancerModeDefault | |
| if v, ok := netMap["loadBalancerMode"]; ok { | |
| n.loadBalancerMode = v.(string) | |
| } | |
| // Reconcile old networks with the recently added `--ipv6` flag | |
| if !n.enableIPv6 { | |
| n.enableIPv6 = len(n.ipamV6Info) > 0 | |
| } | |
| return nil | |
| } | |
| // NetworkOption is an option setter function type used to pass various options to | |
| // NewNetwork method. The various setter functions of type NetworkOption are | |
| // provided by libnetwork, they look like NetworkOptionXXXX(...) | |
| type NetworkOption func(n *network) | |
| // NetworkOptionGeneric function returns an option setter for a Generic option defined | |
| // in a Dictionary of Key-Value pair | |
| func NetworkOptionGeneric(generic map[string]interface{}) NetworkOption { | |
| return func(n *network) { | |
| if n.generic == nil { | |
| n.generic = make(map[string]interface{}) | |
| } | |
| if val, ok := generic[netlabel.EnableIPv6]; ok { | |
| n.enableIPv6 = val.(bool) | |
| } | |
| if val, ok := generic[netlabel.Internal]; ok { | |
| n.internal = val.(bool) | |
| } | |
| for k, v := range generic { | |
| n.generic[k] = v | |
| } | |
| } | |
| } | |
| // NetworkOptionIngress returns an option setter to indicate if a network is | |
| // an ingress network. | |
| func NetworkOptionIngress(ingress bool) NetworkOption { | |
| return func(n *network) { | |
| n.ingress = ingress | |
| } | |
| } | |
| // NetworkOptionPersist returns an option setter to set persistence policy for a network | |
| func NetworkOptionPersist(persist bool) NetworkOption { | |
| return func(n *network) { | |
| n.persist = persist | |
| } | |
| } | |
| // NetworkOptionEnableIPv6 returns an option setter to explicitly configure IPv6 | |
| func NetworkOptionEnableIPv6(enableIPv6 bool) NetworkOption { | |
| return func(n *network) { | |
| if n.generic == nil { | |
| n.generic = make(map[string]interface{}) | |
| } | |
| n.enableIPv6 = enableIPv6 | |
| n.generic[netlabel.EnableIPv6] = enableIPv6 | |
| } | |
| } | |
| // NetworkOptionInternalNetwork returns an option setter to config the network | |
| // to be internal which disables default gateway service | |
| func NetworkOptionInternalNetwork() NetworkOption { | |
| return func(n *network) { | |
| if n.generic == nil { | |
| n.generic = make(map[string]interface{}) | |
| } | |
| n.internal = true | |
| n.generic[netlabel.Internal] = true | |
| } | |
| } | |
| // NetworkOptionAttachable returns an option setter to set attachable for a network | |
| func NetworkOptionAttachable(attachable bool) NetworkOption { | |
| return func(n *network) { | |
| n.attachable = attachable | |
| } | |
| } | |
| // NetworkOptionScope returns an option setter to overwrite the network's scope. | |
| // By default the network's scope is set to the network driver's datascope. | |
| func NetworkOptionScope(scope string) NetworkOption { | |
| return func(n *network) { | |
| n.scope = scope | |
| } | |
| } | |
| // NetworkOptionIpam function returns an option setter for the ipam configuration for this network | |
| func NetworkOptionIpam(ipamDriver string, addrSpace string, ipV4 []*IpamConf, ipV6 []*IpamConf, opts map[string]string) NetworkOption { | |
| return func(n *network) { | |
| if ipamDriver != "" { | |
| n.ipamType = ipamDriver | |
| if ipamDriver == ipamapi.DefaultIPAM { | |
| n.ipamType = defaultIpamForNetworkType(n.Type()) | |
| } | |
| } | |
| n.ipamOptions = opts | |
| n.addrSpace = addrSpace | |
| n.ipamV4Config = ipV4 | |
| n.ipamV6Config = ipV6 | |
| } | |
| } | |
| // NetworkOptionLBEndpoint function returns an option setter for the configuration of the load balancer endpoint for this network | |
| func NetworkOptionLBEndpoint(ip net.IP) NetworkOption { | |
| return func(n *network) { | |
| n.loadBalancerIP = ip | |
| } | |
| } | |
| // NetworkOptionDriverOpts function returns an option setter for any driver parameter described by a map | |
| func NetworkOptionDriverOpts(opts map[string]string) NetworkOption { | |
| return func(n *network) { | |
| if n.generic == nil { | |
| n.generic = make(map[string]interface{}) | |
| } | |
| if opts == nil { | |
| opts = make(map[string]string) | |
| } | |
| // Store the options | |
| n.generic[netlabel.GenericData] = opts | |
| } | |
| } | |
| // NetworkOptionLabels function returns an option setter for labels specific to a network | |
| func NetworkOptionLabels(labels map[string]string) NetworkOption { | |
| return func(n *network) { | |
| n.labels = labels | |
| } | |
| } | |
| // NetworkOptionDynamic function returns an option setter for dynamic option for a network | |
| func NetworkOptionDynamic() NetworkOption { | |
| return func(n *network) { | |
| n.dynamic = true | |
| } | |
| } | |
| // NetworkOptionDeferIPv6Alloc instructs the network to defer the IPV6 address allocation until after the endpoint has been created | |
| // It is being provided to support the specific docker daemon flags where user can deterministically assign an IPv6 address | |
| // to a container as combination of fixed-cidr-v6 + mac-address | |
| // TODO: Remove this option setter once we support endpoint ipam options | |
| func NetworkOptionDeferIPv6Alloc(enable bool) NetworkOption { | |
| return func(n *network) { | |
| n.postIPv6 = enable | |
| } | |
| } | |
| // NetworkOptionConfigOnly tells controller this network is | |
| // a configuration only network. It serves as a configuration | |
| // for other networks. | |
| func NetworkOptionConfigOnly() NetworkOption { | |
| return func(n *network) { | |
| n.configOnly = true | |
| } | |
| } | |
| // NetworkOptionConfigFrom tells controller to pick the | |
| // network configuration from a configuration only network | |
| func NetworkOptionConfigFrom(name string) NetworkOption { | |
| return func(n *network) { | |
| n.configFrom = name | |
| } | |
| } | |
| func (n *network) processOptions(options ...NetworkOption) { | |
| for _, opt := range options { | |
| if opt != nil { | |
| opt(n) | |
| } | |
| } | |
| } | |
| type networkDeleteParams struct { | |
| rmLBEndpoint bool | |
| } | |
| // NetworkDeleteOption is a type for optional parameters to pass to the | |
| // network.Delete() function. | |
| type NetworkDeleteOption func(p *networkDeleteParams) | |
| // NetworkDeleteOptionRemoveLB informs a network.Delete() operation that should | |
| // remove the load balancer endpoint for this network. Note that the Delete() | |
| // method will automatically remove a load balancing endpoint for most networks | |
| // when the network is otherwise empty. However, this does not occur for some | |
| // networks. In particular, networks marked as ingress (which are supposed to | |
| // be more permanent than other overlay networks) won't automatically remove | |
| // the LB endpoint on Delete(). This method allows for explicit removal of | |
| // such networks provided there are no other endpoints present in the network. | |
| // If the network still has non-LB endpoints present, Delete() will not | |
| // remove the LB endpoint and will return an error. | |
| func NetworkDeleteOptionRemoveLB(p *networkDeleteParams) { | |
| p.rmLBEndpoint = true | |
| } | |
| func (n *network) resolveDriver(name string, load bool) (driverapi.Driver, driverapi.Capability, error) { | |
| c := n.getController() | |
| // Check if a driver for the specified network type is available | |
| d, cap := c.drvRegistry.Driver(name) | |
| if d == nil { | |
| if load { | |
| err := c.loadDriver(name) | |
| if err != nil { | |
| return nil, driverapi.Capability{}, err | |
| } | |
| d, cap = c.drvRegistry.Driver(name) | |
| if d == nil { | |
| return nil, driverapi.Capability{}, fmt.Errorf("could not resolve driver %s in registry", name) | |
| } | |
| } else { | |
| // don't fail if driver loading is not required | |
| return nil, driverapi.Capability{}, nil | |
| } | |
| } | |
| return d, cap, nil | |
| } | |
| func (n *network) driverIsMultihost() bool { | |
| _, cap, err := n.resolveDriver(n.networkType, true) | |
| if err != nil { | |
| return false | |
| } | |
| return cap.ConnectivityScope == datastore.GlobalScope | |
| } | |
| func (n *network) driver(load bool) (driverapi.Driver, error) { | |
| d, cap, err := n.resolveDriver(n.networkType, load) | |
| if err != nil { | |
| return nil, err | |
| } | |
| n.mu.Lock() | |
| // If load is not required, driver, cap and err may all be nil | |
| if n.scope == "" { | |
| n.scope = cap.DataScope | |
| } | |
| if n.dynamic { | |
| // If the network is dynamic, then it is swarm | |
| // scoped regardless of the backing driver. | |
| n.scope = datastore.SwarmScope | |
| } | |
| n.mu.Unlock() | |
| return d, nil | |
| } | |
| func (n *network) Delete(options ...NetworkDeleteOption) error { | |
| var params networkDeleteParams | |
| for _, opt := range options { | |
| opt(¶ms) | |
| } | |
| return n.delete(false, params.rmLBEndpoint) | |
| } | |
| // This function gets called in 3 ways: | |
| // - Delete() -- (false, false) | |
| // remove if endpoint count == 0 or endpoint count == 1 and | |
| // there is a load balancer IP | |
| // - Delete(libnetwork.NetworkDeleteOptionRemoveLB) -- (false, true) | |
| // remove load balancer and network if endpoint count == 1 | |
| // - controller.networkCleanup() -- (true, true) | |
| // remove the network no matter what | |
| func (n *network) delete(force bool, rmLBEndpoint bool) error { | |
| n.mu.Lock() | |
| c := n.ctrlr | |
| name := n.name | |
| id := n.id | |
| n.mu.Unlock() | |
| c.networkLocker.Lock(id) | |
| defer c.networkLocker.Unlock(id) //nolint:errcheck | |
| n, err := c.getNetworkFromStore(id) | |
| if err != nil { | |
| return &UnknownNetworkError{name: name, id: id} | |
| } | |
| // Only remove ingress on force removal or explicit LB endpoint removal | |
| if n.ingress && !force && !rmLBEndpoint { | |
| return &ActiveEndpointsError{name: n.name, id: n.id} | |
| } | |
| // Check that the network is empty | |
| var emptyCount uint64 | |
| if n.hasLoadBalancerEndpoint() { | |
| emptyCount = 1 | |
| } | |
| if !force && n.getEpCnt().EndpointCnt() > emptyCount { | |
| if n.configOnly { | |
| return types.ForbiddenErrorf("configuration network %q is in use", n.Name()) | |
| } | |
| return &ActiveEndpointsError{name: n.name, id: n.id} | |
| } | |
| if n.hasLoadBalancerEndpoint() { | |
| // If we got to this point, then the following must hold: | |
| // * force is true OR endpoint count == 1 | |
| if err := n.deleteLoadBalancerSandbox(); err != nil { | |
| if !force { | |
| return err | |
| } | |
| // continue deletion when force is true even on error | |
| logrus.Warnf("Error deleting load balancer sandbox: %v", err) | |
| } | |
| // Reload the network from the store to update the epcnt. | |
| n, err = c.getNetworkFromStore(id) | |
| if err != nil { | |
| return &UnknownNetworkError{name: name, id: id} | |
| } | |
| } | |
| // Up to this point, errors that we returned were recoverable. | |
| // From here on, any errors leave us in an inconsistent state. | |
| // This is unfortunate, but there isn't a safe way to | |
| // reconstitute a load-balancer endpoint after removing it. | |
| // Mark the network for deletion | |
| n.inDelete = true | |
| if err = c.updateToStore(n); err != nil { | |
| return fmt.Errorf("error marking network %s (%s) for deletion: %v", n.Name(), n.ID(), err) | |
| } | |
| if n.ConfigFrom() != "" { | |
| if t, err := c.getConfigNetwork(n.ConfigFrom()); err == nil { | |
| if err := t.getEpCnt().DecEndpointCnt(); err != nil { | |
| logrus.Warnf("Failed to update reference count for configuration network %q on removal of network %q: %v", | |
| t.Name(), n.Name(), err) | |
| } | |
| } else { | |
| logrus.Warnf("Could not find configuration network %q during removal of network %q", n.configFrom, n.Name()) | |
| } | |
| } | |
| if n.configOnly { | |
| goto removeFromStore | |
| } | |
| n.ipamRelease() | |
| // We are about to delete the network. Leave the gossip | |
| // cluster for the network to stop all incoming network | |
| // specific gossip updates before cleaning up all the service | |
| // bindings for the network. But cleanup service binding | |
| // before deleting the network from the store since service | |
| // bindings cleanup requires the network in the store. | |
| n.cancelDriverWatches() | |
| if err = n.leaveCluster(); err != nil { | |
| logrus.Errorf("Failed leaving network %s from the agent cluster: %v", n.Name(), err) | |
| } | |
| // Cleanup the service discovery for this network | |
| c.cleanupServiceDiscovery(n.ID()) | |
| // Cleanup the load balancer. On Windows this call is required | |
| // to remove remote loadbalancers in VFP, and must be performed before | |
| // dataplane network deletion. | |
| if runtime.GOOS == "windows" { | |
| c.cleanupServiceBindings(n.ID()) | |
| } | |
| // Delete the network from the dataplane | |
| if err = n.deleteNetwork(); err != nil { | |
| if !force { | |
| return err | |
| } | |
| logrus.Debugf("driver failed to delete stale network %s (%s): %v", n.Name(), n.ID(), err) | |
| } | |
| removeFromStore: | |
| // deleteFromStore performs an atomic delete operation and the | |
| // network.epCnt will help prevent any possible | |
| // race between endpoint join and network delete | |
| if err = c.deleteFromStore(n.getEpCnt()); err != nil { | |
| if !force { | |
| return fmt.Errorf("error deleting network endpoint count from store: %v", err) | |
| } | |
| logrus.Debugf("Error deleting endpoint count from store for stale network %s (%s) for deletion: %v", n.Name(), n.ID(), err) | |
| } | |
| if err = c.deleteFromStore(n); err != nil { | |
| return fmt.Errorf("error deleting network from store: %v", err) | |
| } | |
| return nil | |
| } | |
| func (n *network) deleteNetwork() error { | |
| d, err := n.driver(true) | |
| if err != nil { | |
| return fmt.Errorf("failed deleting network: %v", err) | |
| } | |
| if err := d.DeleteNetwork(n.ID()); err != nil { | |
| // Forbidden Errors should be honored | |
| if _, ok := err.(types.ForbiddenError); ok { | |
| return err | |
| } | |
| if _, ok := err.(types.MaskableError); !ok { | |
| logrus.Warnf("driver error deleting network %s : %v", n.name, err) | |
| } | |
| } | |
| for _, resolver := range n.resolver { | |
| resolver.Stop() | |
| } | |
| return nil | |
| } | |
| func (n *network) addEndpoint(ep *Endpoint) error { | |
| d, err := n.driver(true) | |
| if err != nil { | |
| return fmt.Errorf("failed to add endpoint: %v", err) | |
| } | |
| err = d.CreateEndpoint(n.id, ep.id, ep.Interface(), ep.generic) | |
| if err != nil { | |
| return types.InternalErrorf("failed to create endpoint %s on network %s: %v", | |
| ep.Name(), n.Name(), err) | |
| } | |
| return nil | |
| } | |
| func (n *network) CreateEndpoint(name string, options ...EndpointOption) (*Endpoint, error) { | |
| var err error | |
| if !config.IsValidName(name) { | |
| return nil, ErrInvalidName(name) | |
| } | |
| if n.ConfigOnly() { | |
| return nil, types.ForbiddenErrorf("cannot create endpoint on configuration-only network") | |
| } | |
| if _, err = n.EndpointByName(name); err == nil { | |
| return nil, types.ForbiddenErrorf("endpoint with name %s already exists in network %s", name, n.Name()) | |
| } | |
| n.ctrlr.networkLocker.Lock(n.id) | |
| defer n.ctrlr.networkLocker.Unlock(n.id) //nolint:errcheck | |
| return n.createEndpoint(name, options...) | |
| } | |
| func (n *network) createEndpoint(name string, options ...EndpointOption) (*Endpoint, error) { | |
| var err error | |
| ep := &Endpoint{name: name, generic: make(map[string]interface{}), iface: &endpointInterface{}} | |
| ep.id = stringid.GenerateRandomID() | |
| // Initialize ep.network with a possibly stale copy of n. We need this to get network from | |
| // store. But once we get it from store we will have the most uptodate copy possibly. | |
| ep.network = n | |
| ep.network, err = ep.getNetworkFromStore() | |
| if err != nil { | |
| logrus.Errorf("failed to get network during CreateEndpoint: %v", err) | |
| return nil, err | |
| } | |
| n = ep.network | |
| ep.processOptions(options...) | |
| for _, llIPNet := range ep.Iface().LinkLocalAddresses() { | |
| if !llIPNet.IP.IsLinkLocalUnicast() { | |
| return nil, types.BadRequestErrorf("invalid link local IP address: %v", llIPNet.IP) | |
| } | |
| } | |
| if opt, ok := ep.generic[netlabel.MacAddress]; ok { | |
| if mac, ok := opt.(net.HardwareAddr); ok { | |
| ep.iface.mac = mac | |
| } | |
| } | |
| ipam, capability, err := n.getController().getIPAMDriver(n.ipamType) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if capability.RequiresMACAddress { | |
| if ep.iface.mac == nil { | |
| ep.iface.mac = netutils.GenerateRandomMAC() | |
| } | |
| if ep.ipamOptions == nil { | |
| ep.ipamOptions = make(map[string]string) | |
| } | |
| ep.ipamOptions[netlabel.MacAddress] = ep.iface.mac.String() | |
| } | |
| if err = ep.assignAddress(ipam, true, n.enableIPv6 && !n.postIPv6); err != nil { | |
| return nil, err | |
| } | |
| defer func() { | |
| if err != nil { | |
| ep.releaseAddress() | |
| } | |
| }() | |
| if err = n.addEndpoint(ep); err != nil { | |
| return nil, err | |
| } | |
| defer func() { | |
| if err != nil { | |
| if e := ep.deleteEndpoint(false); e != nil { | |
| logrus.Warnf("cleaning up endpoint failed %s : %v", name, e) | |
| } | |
| } | |
| }() | |
| // We should perform updateToStore call right after addEndpoint | |
| // in order to have iface properly configured | |
| if err = n.getController().updateToStore(ep); err != nil { | |
| return nil, err | |
| } | |
| defer func() { | |
| if err != nil { | |
| if e := n.getController().deleteFromStore(ep); e != nil { | |
| logrus.Warnf("error rolling back endpoint %s from store: %v", name, e) | |
| } | |
| } | |
| }() | |
| if err = ep.assignAddress(ipam, false, n.enableIPv6 && n.postIPv6); err != nil { | |
| return nil, err | |
| } | |
| // Watch for service records | |
| n.getController().watchSvcRecord(ep) | |
| defer func() { | |
| if err != nil { | |
| n.getController().unWatchSvcRecord(ep) | |
| } | |
| }() | |
| // Increment endpoint count to indicate completion of endpoint addition | |
| if err = n.getEpCnt().IncEndpointCnt(); err != nil { | |
| return nil, err | |
| } | |
| return ep, nil | |
| } | |
| func (n *network) Endpoints() []*Endpoint { | |
| endpoints, err := n.getEndpointsFromStore() | |
| if err != nil { | |
| logrus.Error(err) | |
| } | |
| return endpoints | |
| } | |
| func (n *network) WalkEndpoints(walker EndpointWalker) { | |
| for _, e := range n.Endpoints() { | |
| if walker(e) { | |
| return | |
| } | |
| } | |
| } | |
| func (n *network) EndpointByName(name string) (*Endpoint, error) { | |
| if name == "" { | |
| return nil, ErrInvalidName(name) | |
| } | |
| var e *Endpoint | |
| s := func(current *Endpoint) bool { | |
| if current.Name() == name { | |
| e = current | |
| return true | |
| } | |
| return false | |
| } | |
| n.WalkEndpoints(s) | |
| if e == nil { | |
| return nil, ErrNoSuchEndpoint(name) | |
| } | |
| return e, nil | |
| } | |
| func (n *network) EndpointByID(id string) (*Endpoint, error) { | |
| if id == "" { | |
| return nil, ErrInvalidID(id) | |
| } | |
| ep, err := n.getEndpointFromStore(id) | |
| if err != nil { | |
| return nil, ErrNoSuchEndpoint(id) | |
| } | |
| return ep, nil | |
| } | |
| func (n *network) updateSvcRecord(ep *Endpoint, localEps []*Endpoint, isAdd bool) { | |
| var ipv6 net.IP | |
| epName := ep.Name() | |
| if iface := ep.Iface(); iface != nil && iface.Address() != nil { | |
| myAliases := ep.MyAliases() | |
| if iface.AddressIPv6() != nil { | |
| ipv6 = iface.AddressIPv6().IP | |
| } | |
| serviceID := ep.svcID | |
| if serviceID == "" { | |
| serviceID = ep.ID() | |
| } | |
| if isAdd { | |
| // If anonymous endpoint has an alias use the first alias | |
| // for ip->name mapping. Not having the reverse mapping | |
| // breaks some apps | |
| if ep.isAnonymous() { | |
| if len(myAliases) > 0 { | |
| n.addSvcRecords(ep.ID(), myAliases[0], serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord") | |
| } | |
| } else { | |
| n.addSvcRecords(ep.ID(), epName, serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord") | |
| } | |
| for _, alias := range myAliases { | |
| n.addSvcRecords(ep.ID(), alias, serviceID, iface.Address().IP, ipv6, false, "updateSvcRecord") | |
| } | |
| } else { | |
| if ep.isAnonymous() { | |
| if len(myAliases) > 0 { | |
| n.deleteSvcRecords(ep.ID(), myAliases[0], serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord") | |
| } | |
| } else { | |
| n.deleteSvcRecords(ep.ID(), epName, serviceID, iface.Address().IP, ipv6, true, "updateSvcRecord") | |
| } | |
| for _, alias := range myAliases { | |
| n.deleteSvcRecords(ep.ID(), alias, serviceID, iface.Address().IP, ipv6, false, "updateSvcRecord") | |
| } | |
| } | |
| } | |
| } | |
| func addIPToName(ipMap *setmatrix.SetMatrix[ipInfo], name, serviceID string, ip net.IP) { | |
| reverseIP := netutils.ReverseIP(ip.String()) | |
| ipMap.Insert(reverseIP, ipInfo{ | |
| name: name, | |
| serviceID: serviceID, | |
| }) | |
| } | |
| func delIPToName(ipMap *setmatrix.SetMatrix[ipInfo], name, serviceID string, ip net.IP) { | |
| reverseIP := netutils.ReverseIP(ip.String()) | |
| ipMap.Remove(reverseIP, ipInfo{ | |
| name: name, | |
| serviceID: serviceID, | |
| }) | |
| } | |
| func addNameToIP(svcMap *setmatrix.SetMatrix[svcMapEntry], name, serviceID string, epIP net.IP) { | |
| // Since DNS name resolution is case-insensitive, Use the lower-case form | |
| // of the name as the key into svcMap | |
| lowerCaseName := strings.ToLower(name) | |
| svcMap.Insert(lowerCaseName, svcMapEntry{ | |
| ip: epIP.String(), | |
| serviceID: serviceID, | |
| }) | |
| } | |
| func delNameToIP(svcMap *setmatrix.SetMatrix[svcMapEntry], name, serviceID string, epIP net.IP) { | |
| lowerCaseName := strings.ToLower(name) | |
| svcMap.Remove(lowerCaseName, svcMapEntry{ | |
| ip: epIP.String(), | |
| serviceID: serviceID, | |
| }) | |
| } | |
| func (n *network) addSvcRecords(eID, name, serviceID string, epIP, epIPv6 net.IP, ipMapUpdate bool, method string) { | |
| // Do not add service names for ingress network as this is a | |
| // routing only network | |
| if n.ingress { | |
| return | |
| } | |
| networkID := n.ID() | |
| logrus.Debugf("%s (%.7s).addSvcRecords(%s, %s, %s, %t) %s sid:%s", eID, networkID, name, epIP, epIPv6, ipMapUpdate, method, serviceID) | |
| c := n.getController() | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| sr, ok := c.svcRecords[networkID] | |
| if !ok { | |
| sr = &svcInfo{} | |
| c.svcRecords[networkID] = sr | |
| } | |
| if ipMapUpdate { | |
| addIPToName(&sr.ipMap, name, serviceID, epIP) | |
| if epIPv6 != nil { | |
| addIPToName(&sr.ipMap, name, serviceID, epIPv6) | |
| } | |
| } | |
| addNameToIP(&sr.svcMap, name, serviceID, epIP) | |
| if epIPv6 != nil { | |
| addNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6) | |
| } | |
| } | |
| func (n *network) deleteSvcRecords(eID, name, serviceID string, epIP net.IP, epIPv6 net.IP, ipMapUpdate bool, method string) { | |
| // Do not delete service names from ingress network as this is a | |
| // routing only network | |
| if n.ingress { | |
| return | |
| } | |
| networkID := n.ID() | |
| logrus.Debugf("%s (%.7s).deleteSvcRecords(%s, %s, %s, %t) %s sid:%s ", eID, networkID, name, epIP, epIPv6, ipMapUpdate, method, serviceID) | |
| c := n.getController() | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| sr, ok := c.svcRecords[networkID] | |
| if !ok { | |
| return | |
| } | |
| if ipMapUpdate { | |
| delIPToName(&sr.ipMap, name, serviceID, epIP) | |
| if epIPv6 != nil { | |
| delIPToName(&sr.ipMap, name, serviceID, epIPv6) | |
| } | |
| } | |
| delNameToIP(&sr.svcMap, name, serviceID, epIP) | |
| if epIPv6 != nil { | |
| delNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6) | |
| } | |
| } | |
| func (n *network) getSvcRecords(ep *Endpoint) []etchosts.Record { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| if ep == nil { | |
| return nil | |
| } | |
| var recs []etchosts.Record | |
| epName := ep.Name() | |
| n.ctrlr.mu.Lock() | |
| defer n.ctrlr.mu.Unlock() | |
| sr, ok := n.ctrlr.svcRecords[n.id] | |
| if !ok { | |
| return nil | |
| } | |
| svcMapKeys := sr.svcMap.Keys() | |
| // Loop on service names on this network | |
| for _, k := range svcMapKeys { | |
| if strings.Split(k, ".")[0] == epName { | |
| continue | |
| } | |
| // Get all the IPs associated to this service | |
| mapEntryList, ok := sr.svcMap.Get(k) | |
| if !ok { | |
| // The key got deleted | |
| continue | |
| } | |
| if len(mapEntryList) == 0 { | |
| logrus.Warnf("Found empty list of IP addresses for service %s on network %s (%s)", k, n.name, n.id) | |
| continue | |
| } | |
| recs = append(recs, etchosts.Record{ | |
| Hosts: k, | |
| IP: mapEntryList[0].ip, | |
| }) | |
| } | |
| return recs | |
| } | |
| func (n *network) getController() *Controller { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.ctrlr | |
| } | |
| func (n *network) ipamAllocate() error { | |
| if n.hasSpecialDriver() { | |
| return nil | |
| } | |
| ipam, _, err := n.getController().getIPAMDriver(n.ipamType) | |
| if err != nil { | |
| return err | |
| } | |
| if n.addrSpace == "" { | |
| if n.addrSpace, err = n.deriveAddressSpace(); err != nil { | |
| return err | |
| } | |
| } | |
| err = n.ipamAllocateVersion(4, ipam) | |
| if err != nil { | |
| return err | |
| } | |
| defer func() { | |
| if err != nil { | |
| n.ipamReleaseVersion(4, ipam) | |
| } | |
| }() | |
| if !n.enableIPv6 { | |
| return nil | |
| } | |
| err = n.ipamAllocateVersion(6, ipam) | |
| return err | |
| } | |
| func (n *network) requestPoolHelper(ipam ipamapi.Ipam, addressSpace, preferredPool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) { | |
| for { | |
| poolID, pool, meta, err := ipam.RequestPool(addressSpace, preferredPool, subPool, options, v6) | |
| if err != nil { | |
| return "", nil, nil, err | |
| } | |
| // If the network belongs to global scope or the pool was | |
| // explicitly chosen or it is invalid, do not perform the overlap check. | |
| if n.Scope() == datastore.GlobalScope || preferredPool != "" || !types.IsIPNetValid(pool) { | |
| return poolID, pool, meta, nil | |
| } | |
| // Check for overlap and if none found, we have found the right pool. | |
| if _, err := netutils.FindAvailableNetwork([]*net.IPNet{pool}); err == nil { | |
| return poolID, pool, meta, nil | |
| } | |
| // Pool obtained in this iteration is | |
| // overlapping. Hold onto the pool and don't release | |
| // it yet, because we don't want ipam to give us back | |
| // the same pool over again. But make sure we still do | |
| // a deferred release when we have either obtained a | |
| // non-overlapping pool or ran out of pre-defined | |
| // pools. | |
| defer func() { | |
| if err := ipam.ReleasePool(poolID); err != nil { | |
| logrus.Warnf("Failed to release overlapping pool %s while returning from pool request helper for network %s", pool, n.Name()) | |
| } | |
| }() | |
| // If this is a preferred pool request and the network | |
| // is local scope and there is an overlap, we fail the | |
| // network creation right here. The pool will be | |
| // released in the defer. | |
| if preferredPool != "" { | |
| return "", nil, nil, fmt.Errorf("requested subnet %s overlaps in the host", preferredPool) | |
| } | |
| } | |
| } | |
| func (n *network) ipamAllocateVersion(ipVer int, ipam ipamapi.Ipam) error { | |
| var ( | |
| cfgList *[]*IpamConf | |
| infoList *[]*IpamInfo | |
| err error | |
| ) | |
| switch ipVer { | |
| case 4: | |
| cfgList = &n.ipamV4Config | |
| infoList = &n.ipamV4Info | |
| case 6: | |
| cfgList = &n.ipamV6Config | |
| infoList = &n.ipamV6Info | |
| default: | |
| return types.InternalErrorf("incorrect ip version passed to ipam allocate: %d", ipVer) | |
| } | |
| if len(*cfgList) == 0 { | |
| *cfgList = []*IpamConf{{}} | |
| } | |
| *infoList = make([]*IpamInfo, len(*cfgList)) | |
| logrus.Debugf("Allocating IPv%d pools for network %s (%s)", ipVer, n.Name(), n.ID()) | |
| for i, cfg := range *cfgList { | |
| if err = cfg.Validate(); err != nil { | |
| return err | |
| } | |
| d := &IpamInfo{} | |
| (*infoList)[i] = d | |
| d.AddressSpace = n.addrSpace | |
| d.PoolID, d.Pool, d.Meta, err = n.requestPoolHelper(ipam, n.addrSpace, cfg.PreferredPool, cfg.SubPool, n.ipamOptions, ipVer == 6) | |
| if err != nil { | |
| return err | |
| } | |
| defer func() { | |
| if err != nil { | |
| if err := ipam.ReleasePool(d.PoolID); err != nil { | |
| logrus.Warnf("Failed to release address pool %s after failure to create network %s (%s)", d.PoolID, n.Name(), n.ID()) | |
| } | |
| } | |
| }() | |
| if gws, ok := d.Meta[netlabel.Gateway]; ok { | |
| if d.Gateway, err = types.ParseCIDR(gws); err != nil { | |
| return types.BadRequestErrorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err) | |
| } | |
| } | |
| // If user requested a specific gateway, libnetwork will allocate it | |
| // irrespective of whether ipam driver returned a gateway already. | |
| // If none of the above is true, libnetwork will allocate one. | |
| if cfg.Gateway != "" || d.Gateway == nil { | |
| var gatewayOpts = map[string]string{ | |
| ipamapi.RequestAddressType: netlabel.Gateway, | |
| } | |
| if d.Gateway, _, err = ipam.RequestAddress(d.PoolID, net.ParseIP(cfg.Gateway), gatewayOpts); err != nil { | |
| return types.InternalErrorf("failed to allocate gateway (%v): %v", cfg.Gateway, err) | |
| } | |
| } | |
| // Auxiliary addresses must be part of the master address pool | |
| // If they fall into the container addressable pool, libnetwork will reserve them | |
| if cfg.AuxAddresses != nil { | |
| var ip net.IP | |
| d.IPAMData.AuxAddresses = make(map[string]*net.IPNet, len(cfg.AuxAddresses)) | |
| for k, v := range cfg.AuxAddresses { | |
| if ip = net.ParseIP(v); ip == nil { | |
| return types.BadRequestErrorf("non parsable secondary ip address (%s:%s) passed for network %s", k, v, n.Name()) | |
| } | |
| if !d.Pool.Contains(ip) { | |
| return types.ForbiddenErrorf("auxiliary address: (%s:%s) must belong to the master pool: %s", k, v, d.Pool) | |
| } | |
| // Attempt reservation in the container addressable pool, silent the error if address does not belong to that pool | |
| if d.IPAMData.AuxAddresses[k], _, err = ipam.RequestAddress(d.PoolID, ip, nil); err != nil && err != ipamapi.ErrIPOutOfRange { | |
| return types.InternalErrorf("failed to allocate secondary ip address (%s:%s): %v", k, v, err) | |
| } | |
| } | |
| } | |
| } | |
| return nil | |
| } | |
| func (n *network) ipamRelease() { | |
| if n.hasSpecialDriver() { | |
| return | |
| } | |
| ipam, _, err := n.getController().getIPAMDriver(n.ipamType) | |
| if err != nil { | |
| logrus.Warnf("Failed to retrieve ipam driver to release address pool(s) on delete of network %s (%s): %v", n.Name(), n.ID(), err) | |
| return | |
| } | |
| n.ipamReleaseVersion(4, ipam) | |
| n.ipamReleaseVersion(6, ipam) | |
| } | |
| func (n *network) ipamReleaseVersion(ipVer int, ipam ipamapi.Ipam) { | |
| var infoList *[]*IpamInfo | |
| switch ipVer { | |
| case 4: | |
| infoList = &n.ipamV4Info | |
| case 6: | |
| infoList = &n.ipamV6Info | |
| default: | |
| logrus.Warnf("incorrect ip version passed to ipam release: %d", ipVer) | |
| return | |
| } | |
| if len(*infoList) == 0 { | |
| return | |
| } | |
| logrus.Debugf("releasing IPv%d pools from network %s (%s)", ipVer, n.Name(), n.ID()) | |
| for _, d := range *infoList { | |
| if d.Gateway != nil { | |
| if err := ipam.ReleaseAddress(d.PoolID, d.Gateway.IP); err != nil { | |
| logrus.Warnf("Failed to release gateway ip address %s on delete of network %s (%s): %v", d.Gateway.IP, n.Name(), n.ID(), err) | |
| } | |
| } | |
| if d.IPAMData.AuxAddresses != nil { | |
| for k, nw := range d.IPAMData.AuxAddresses { | |
| if d.Pool.Contains(nw.IP) { | |
| if err := ipam.ReleaseAddress(d.PoolID, nw.IP); err != nil && err != ipamapi.ErrIPOutOfRange { | |
| logrus.Warnf("Failed to release secondary ip address %s (%v) on delete of network %s (%s): %v", k, nw.IP, n.Name(), n.ID(), err) | |
| } | |
| } | |
| } | |
| } | |
| if err := ipam.ReleasePool(d.PoolID); err != nil { | |
| logrus.Warnf("Failed to release address pool %s on delete of network %s (%s): %v", d.PoolID, n.Name(), n.ID(), err) | |
| } | |
| } | |
| *infoList = nil | |
| } | |
| func (n *network) getIPInfo(ipVer int) []*IpamInfo { | |
| var info []*IpamInfo | |
| switch ipVer { | |
| case 4: | |
| info = n.ipamV4Info | |
| case 6: | |
| info = n.ipamV6Info | |
| default: | |
| return nil | |
| } | |
| l := make([]*IpamInfo, 0, len(info)) | |
| n.mu.Lock() | |
| l = append(l, info...) | |
| n.mu.Unlock() | |
| return l | |
| } | |
| func (n *network) getIPData(ipVer int) []driverapi.IPAMData { | |
| var info []*IpamInfo | |
| switch ipVer { | |
| case 4: | |
| info = n.ipamV4Info | |
| case 6: | |
| info = n.ipamV6Info | |
| default: | |
| return nil | |
| } | |
| l := make([]driverapi.IPAMData, 0, len(info)) | |
| n.mu.Lock() | |
| for _, d := range info { | |
| l = append(l, d.IPAMData) | |
| } | |
| n.mu.Unlock() | |
| return l | |
| } | |
| func (n *network) deriveAddressSpace() (string, error) { | |
| ipam, _ := n.getController().ipamRegistry.IPAM(n.ipamType) | |
| if ipam == nil { | |
| return "", types.NotFoundErrorf("failed to get default address space: unknown ipam type %q", n.ipamType) | |
| } | |
| local, global, err := ipam.GetDefaultAddressSpaces() | |
| if err != nil { | |
| return "", types.NotFoundErrorf("failed to get default address space: %v", err) | |
| } | |
| if n.DataScope() == datastore.GlobalScope { | |
| return global, nil | |
| } | |
| return local, nil | |
| } | |
| func (n *network) Info() NetworkInfo { | |
| return n | |
| } | |
| func (n *network) Peers() []networkdb.PeerInfo { | |
| if !n.Dynamic() { | |
| return []networkdb.PeerInfo{} | |
| } | |
| agent := n.getController().getAgent() | |
| if agent == nil { | |
| return []networkdb.PeerInfo{} | |
| } | |
| return agent.networkDB.Peers(n.ID()) | |
| } | |
| func (n *network) DriverOptions() map[string]string { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| if n.generic != nil { | |
| if m, ok := n.generic[netlabel.GenericData]; ok { | |
| return m.(map[string]string) | |
| } | |
| } | |
| return map[string]string{} | |
| } | |
| func (n *network) Scope() string { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.scope | |
| } | |
| func (n *network) IpamConfig() (string, map[string]string, []*IpamConf, []*IpamConf) { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| v4L := make([]*IpamConf, len(n.ipamV4Config)) | |
| v6L := make([]*IpamConf, len(n.ipamV6Config)) | |
| for i, c := range n.ipamV4Config { | |
| cc := &IpamConf{} | |
| if err := c.CopyTo(cc); err != nil { | |
| logrus.WithError(err).Error("Error copying ipam ipv4 config") | |
| } | |
| v4L[i] = cc | |
| } | |
| for i, c := range n.ipamV6Config { | |
| cc := &IpamConf{} | |
| if err := c.CopyTo(cc); err != nil { | |
| logrus.WithError(err).Debug("Error copying ipam ipv6 config") | |
| } | |
| v6L[i] = cc | |
| } | |
| return n.ipamType, n.ipamOptions, v4L, v6L | |
| } | |
| func (n *network) IpamInfo() ([]*IpamInfo, []*IpamInfo) { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| v4Info := make([]*IpamInfo, len(n.ipamV4Info)) | |
| v6Info := make([]*IpamInfo, len(n.ipamV6Info)) | |
| for i, info := range n.ipamV4Info { | |
| ic := &IpamInfo{} | |
| if err := info.CopyTo(ic); err != nil { | |
| logrus.WithError(err).Error("Error copying ipv4 ipam config") | |
| } | |
| v4Info[i] = ic | |
| } | |
| for i, info := range n.ipamV6Info { | |
| ic := &IpamInfo{} | |
| if err := info.CopyTo(ic); err != nil { | |
| logrus.WithError(err).Error("Error copying ipv6 ipam config") | |
| } | |
| v6Info[i] = ic | |
| } | |
| return v4Info, v6Info | |
| } | |
| func (n *network) Internal() bool { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.internal | |
| } | |
| func (n *network) Attachable() bool { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.attachable | |
| } | |
| func (n *network) Ingress() bool { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.ingress | |
| } | |
| func (n *network) Dynamic() bool { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.dynamic | |
| } | |
| func (n *network) IPv6Enabled() bool { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.enableIPv6 | |
| } | |
| func (n *network) ConfigFrom() string { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.configFrom | |
| } | |
| func (n *network) ConfigOnly() bool { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| return n.configOnly | |
| } | |
| func (n *network) Labels() map[string]string { | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| var lbls = make(map[string]string, len(n.labels)) | |
| for k, v := range n.labels { | |
| lbls[k] = v | |
| } | |
| return lbls | |
| } | |
| func (n *network) TableEventRegister(tableName string, objType driverapi.ObjectType) error { | |
| if !driverapi.IsValidType(objType) { | |
| return fmt.Errorf("invalid object type %v in registering table, %s", objType, tableName) | |
| } | |
| t := networkDBTable{ | |
| name: tableName, | |
| objType: objType, | |
| } | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| n.driverTables = append(n.driverTables, t) | |
| return nil | |
| } | |
| func (n *network) UpdateIpamConfig(ipV4Data []driverapi.IPAMData) { | |
| ipamV4Config := make([]*IpamConf, len(ipV4Data)) | |
| for i, data := range ipV4Data { | |
| ic := &IpamConf{} | |
| ic.PreferredPool = data.Pool.String() | |
| ic.Gateway = data.Gateway.IP.String() | |
| ipamV4Config[i] = ic | |
| } | |
| n.mu.Lock() | |
| defer n.mu.Unlock() | |
| n.ipamV4Config = ipamV4Config | |
| } | |
| // Special drivers are ones which do not need to perform any network plumbing | |
| func (n *network) hasSpecialDriver() bool { | |
| return n.Type() == "host" || n.Type() == "null" | |
| } | |
| func (n *network) hasLoadBalancerEndpoint() bool { | |
| return len(n.loadBalancerIP) != 0 | |
| } | |
| func (n *network) ResolveName(req string, ipType int) ([]net.IP, bool) { | |
| var ipv6Miss bool | |
| c := n.getController() | |
| networkID := n.ID() | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| sr, ok := c.svcRecords[networkID] | |
| if !ok { | |
| return nil, false | |
| } | |
| req = strings.TrimSuffix(req, ".") | |
| req = strings.ToLower(req) | |
| ipSet, ok := sr.svcMap.Get(req) | |
| if ipType == types.IPv6 { | |
| // If the name resolved to v4 address then its a valid name in | |
| // the docker network domain. If the network is not v6 enabled | |
| // set ipv6Miss to filter the DNS query from going to external | |
| // resolvers. | |
| if ok && !n.enableIPv6 { | |
| ipv6Miss = true | |
| } | |
| ipSet, ok = sr.svcIPv6Map.Get(req) | |
| } | |
| if ok && len(ipSet) > 0 { | |
| // this map is to avoid IP duplicates, this can happen during a transition period where 2 services are using the same IP | |
| noDup := make(map[string]bool) | |
| var ipLocal []net.IP | |
| for _, ip := range ipSet { | |
| if _, dup := noDup[ip.ip]; !dup { | |
| noDup[ip.ip] = true | |
| ipLocal = append(ipLocal, net.ParseIP(ip.ip)) | |
| } | |
| } | |
| return ipLocal, ok | |
| } | |
| return nil, ipv6Miss | |
| } | |
| func (n *network) HandleQueryResp(name string, ip net.IP) { | |
| networkID := n.ID() | |
| c := n.getController() | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| sr, ok := c.svcRecords[networkID] | |
| if !ok { | |
| return | |
| } | |
| ipStr := netutils.ReverseIP(ip.String()) | |
| // If an object with extResolver == true is already in the set this call will fail | |
| // but anyway it means that has already been inserted before | |
| if ok, _ := sr.ipMap.Contains(ipStr, ipInfo{name: name}); ok { | |
| sr.ipMap.Remove(ipStr, ipInfo{name: name}) | |
| sr.ipMap.Insert(ipStr, ipInfo{name: name, extResolver: true}) | |
| } | |
| } | |
| func (n *network) ResolveIP(ip string) string { | |
| networkID := n.ID() | |
| c := n.getController() | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| sr, ok := c.svcRecords[networkID] | |
| if !ok { | |
| return "" | |
| } | |
| nwName := n.Name() | |
| elemSet, ok := sr.ipMap.Get(ip) | |
| if !ok || len(elemSet) == 0 { | |
| return "" | |
| } | |
| // NOTE it is possible to have more than one element in the Set, this will happen | |
| // because of interleave of different events from different sources (local container create vs | |
| // network db notifications) | |
| // In such cases the resolution will be based on the first element of the set, and can vary | |
| // during the system stabilitation | |
| elem := elemSet[0] | |
| if elem.extResolver { | |
| return "" | |
| } | |
| return elem.name + "." + nwName | |
| } | |
| func (n *network) ResolveService(name string) ([]*net.SRV, []net.IP) { | |
| c := n.getController() | |
| srv := []*net.SRV{} | |
| ip := []net.IP{} | |
| logrus.Debugf("Service name To resolve: %v", name) | |
| // There are DNS implementations that allow SRV queries for names not in | |
| // the format defined by RFC 2782. Hence specific validations checks are | |
| // not done | |
| parts := strings.Split(name, ".") | |
| if len(parts) < 3 { | |
| return nil, nil | |
| } | |
| portName := parts[0] | |
| proto := parts[1] | |
| svcName := strings.Join(parts[2:], ".") | |
| networkID := n.ID() | |
| c.mu.Lock() | |
| defer c.mu.Unlock() | |
| sr, ok := c.svcRecords[networkID] | |
| if !ok { | |
| return nil, nil | |
| } | |
| svcs, ok := sr.service[svcName] | |
| if !ok { | |
| return nil, nil | |
| } | |
| for _, svc := range svcs { | |
| if svc.portName != portName { | |
| continue | |
| } | |
| if svc.proto != proto { | |
| continue | |
| } | |
| for _, t := range svc.target { | |
| srv = append(srv, | |
| &net.SRV{ | |
| Target: t.name, | |
| Port: t.port, | |
| }) | |
| ip = append(ip, t.ip) | |
| } | |
| } | |
| return srv, ip | |
| } | |
| func (n *network) ExecFunc(f func()) error { | |
| return types.NotImplementedErrorf("ExecFunc not supported by network") | |
| } | |
| func (n *network) NdotsSet() bool { | |
| return false | |
| } | |
| // config-only network is looked up by name | |
| func (c *Controller) getConfigNetwork(name string) (*network, error) { | |
| var n Network | |
| s := func(current Network) bool { | |
| if current.Info().ConfigOnly() && current.Name() == name { | |
| n = current | |
| return true | |
| } | |
| return false | |
| } | |
| c.WalkNetworks(s) | |
| if n == nil { | |
| return nil, types.NotFoundErrorf("configuration network %q not found", name) | |
| } | |
| return n.(*network), nil | |
| } | |
| func (n *network) lbSandboxName() string { | |
| name := "lb-" + n.name | |
| if n.ingress { | |
| name = n.name + "-sbox" | |
| } | |
| return name | |
| } | |
| func (n *network) lbEndpointName() string { | |
| return n.name + "-endpoint" | |
| } | |
| func (n *network) createLoadBalancerSandbox() (retErr error) { | |
| sandboxName := n.lbSandboxName() | |
| // Mark the sandbox to be a load balancer | |
| sbOptions := []SandboxOption{OptionLoadBalancer(n.id)} | |
| if n.ingress { | |
| sbOptions = append(sbOptions, OptionIngress()) | |
| } | |
| sb, err := n.ctrlr.NewSandbox(sandboxName, sbOptions...) | |
| if err != nil { | |
| return err | |
| } | |
| defer func() { | |
| if retErr != nil { | |
| if e := n.ctrlr.SandboxDestroy(sandboxName); e != nil { | |
| logrus.Warnf("could not delete sandbox %s on failure on failure (%v): %v", sandboxName, retErr, e) | |
| } | |
| } | |
| }() | |
| endpointName := n.lbEndpointName() | |
| epOptions := []EndpointOption{ | |
| CreateOptionIpam(n.loadBalancerIP, nil, nil, nil), | |
| CreateOptionLoadBalancer(), | |
| } | |
| if n.hasLoadBalancerEndpoint() && !n.ingress { | |
| // Mark LB endpoints as anonymous so they don't show up in DNS | |
| epOptions = append(epOptions, CreateOptionAnonymous()) | |
| } | |
| ep, err := n.createEndpoint(endpointName, epOptions...) | |
| if err != nil { | |
| return err | |
| } | |
| defer func() { | |
| if retErr != nil { | |
| if e := ep.Delete(true); e != nil { | |
| logrus.Warnf("could not delete endpoint %s on failure on failure (%v): %v", endpointName, retErr, e) | |
| } | |
| } | |
| }() | |
| if err := ep.Join(sb, nil); err != nil { | |
| return err | |
| } | |
| return sb.EnableService() | |
| } | |
| func (n *network) deleteLoadBalancerSandbox() error { | |
| n.mu.Lock() | |
| c := n.ctrlr | |
| name := n.name | |
| n.mu.Unlock() | |
| sandboxName := n.lbSandboxName() | |
| endpointName := n.lbEndpointName() | |
| endpoint, err := n.EndpointByName(endpointName) | |
| if err != nil { | |
| logrus.Warnf("Failed to find load balancer endpoint %s on network %s: %v", endpointName, name, err) | |
| } else { | |
| info := endpoint.Info() | |
| if info != nil { | |
| sb := info.Sandbox() | |
| if sb != nil { | |
| if err := sb.DisableService(); err != nil { | |
| logrus.Warnf("Failed to disable service on sandbox %s: %v", sandboxName, err) | |
| // Ignore error and attempt to delete the load balancer endpoint | |
| } | |
| } | |
| } | |
| if err := endpoint.Delete(true); err != nil { | |
| logrus.Warnf("Failed to delete endpoint %s (%s) in %s: %v", endpoint.Name(), endpoint.ID(), sandboxName, err) | |
| // Ignore error and attempt to delete the sandbox. | |
| } | |
| } | |
| if err := c.SandboxDestroy(sandboxName); err != nil { | |
| return fmt.Errorf("Failed to delete %s sandbox: %v", sandboxName, err) | |
| } | |
| return nil | |
| } |