| // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: |
| //go:build go1.23 |
| |
| package libnetwork |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "net" |
| "net/netip" |
| "runtime" |
| "strings" |
| "sync" |
| "time" |
| |
| "github.com/containerd/log" |
| "github.com/docker/docker/errdefs" |
| "github.com/docker/docker/internal/sliceutil" |
| "github.com/docker/docker/libnetwork/datastore" |
| "github.com/docker/docker/libnetwork/driverapi" |
| "github.com/docker/docker/libnetwork/internal/netiputil" |
| "github.com/docker/docker/libnetwork/internal/setmatrix" |
| "github.com/docker/docker/libnetwork/ipamapi" |
| "github.com/docker/docker/libnetwork/ipams/defaultipam" |
| "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/scope" |
| "github.com/docker/docker/libnetwork/types" |
| "github.com/docker/docker/pkg/stringid" |
| "go.opentelemetry.io/otel" |
| "go.opentelemetry.io/otel/attribute" |
| "go.opentelemetry.io/otel/trace" |
| ) |
| |
| // 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 |
| // |
| // TODO(aker): use proper net/* structs instead of string literals. |
| 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 for automatic address allocations. |
| 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 != "" && net.ParseIP(c.Gateway) == nil { |
| return types.InvalidParameterErrorf("invalid gateway address %s in Ipam configuration", c.Gateway) |
| } |
| return nil |
| } |
| |
| // Contains checks whether the ipam master address pool contains [addr]. |
| func (c *IpamConf) Contains(addr net.IP) bool { |
| if c == nil { |
| return false |
| } |
| if c.PreferredPool == "" { |
| return false |
| } |
| |
| _, allowedRange, _ := net.ParseCIDR(c.PreferredPool) |
| |
| return allowedRange.Contains(addr) |
| } |
| |
| // IsStatic checks whether the subnet was statically allocated (ie. user-defined). |
| func (c *IpamConf) IsStatic() bool { |
| return c != nil && c.PreferredPool != "" |
| } |
| |
| // 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]any{ |
| "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]any |
| 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) //nolint:errchkjson // FIXME: handle json (Un)Marshal errors |
| 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 |
| } |
| |
| // Network represents a logical connectivity zone that containers may |
| // join using the Link method. A network is managed by a specific driver. |
| type Network struct { |
| ctrlr *Controller |
| name string |
| networkType string // networkType is the name of the netdriver used by this network |
| id string |
| created time.Time |
| scope string // network data scope |
| labels map[string]string |
| ipamType string // ipamType is the name of the IPAM driver |
| ipamOptions map[string]string |
| addrSpace string |
| ipamV4Config []*IpamConf |
| ipamV6Config []*IpamConf |
| ipamV4Info []*IpamInfo |
| ipamV6Info []*IpamInfo |
| enableIPv4 bool |
| enableIPv6 bool |
| generic options.Generic |
| dbIndex uint64 |
| dbExists bool |
| persist bool |
| drvOnce *sync.Once |
| resolver []*Resolver |
| internal bool |
| attachable bool |
| inDelete bool |
| ingress bool |
| driverTables []networkDBTable |
| dynamic bool |
| configOnly bool |
| configFrom string |
| loadBalancerIP net.IP |
| loadBalancerMode string |
| skipGwAllocIPv4 bool |
| skipGwAllocIPv6 bool |
| platformNetwork //nolint:nolintlint,unused // only populated on windows |
| mu sync.Mutex |
| } |
| |
| const ( |
| loadBalancerModeNAT = "NAT" |
| loadBalancerModeDSR = "DSR" |
| loadBalancerModeDefault = loadBalancerModeNAT |
| ) |
| |
| // Name returns a user chosen name for this network. |
| func (n *Network) Name() string { |
| n.mu.Lock() |
| defer n.mu.Unlock() |
| |
| return n.name |
| } |
| |
| // ID returns a system generated id for this network. |
| 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 |
| } |
| |
| // Type returns the type of network, which corresponds to its managing driver. |
| func (n *Network) Type() string { |
| n.mu.Lock() |
| defer n.mu.Unlock() |
| |
| return n.networkType |
| } |
| |
| func (n *Network) Resolvers() []*Resolver { |
| n.mu.Lock() |
| defer n.mu.Unlock() |
| |
| return n.resolver |
| } |
| |
| 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 err := n.validateAdvertiseAddrConfig(); err != nil { |
| return err |
| } |
| } else { |
| if n.configOnly { |
| return types.ForbiddenErrorf("a configuration network cannot depend on another configuration network") |
| } |
| // Check that no config has been set for this --config-from network. |
| // (Note that the default for enableIPv4 is 'true', ipamType has its own default, |
| // and other settings are zero valued by default.) |
| if n.ipamType != "" && |
| n.ipamType != defaultIpamForNetworkType(n.networkType) || |
| !n.enableIPv4 || 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 any |
| ) |
| switch t := data.(type) { |
| case map[string]any, 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.enableIPv4 = n.enableIPv4 |
| 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 n.ipamType != "" { |
| 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 |
| } |
| } |
| |
| // Network drivers only see generic flags. So, make sure they match. |
| if to.generic == nil { |
| to.generic = options.Generic{} |
| } |
| to.generic[netlabel.Internal] = to.internal |
| to.generic[netlabel.EnableIPv4] = to.enableIPv4 |
| to.generic[netlabel.EnableIPv6] = to.enableIPv6 |
| |
| 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.enableIPv4 = n.enableIPv4 |
| dstN.enableIPv6 = n.enableIPv6 |
| dstN.persist = n.persist |
| 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 |
| dstN.skipGwAllocIPv4 = n.skipGwAllocIPv4 |
| dstN.skipGwAllocIPv6 = n.skipGwAllocIPv6 |
| |
| // 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) validateAdvertiseAddrConfig() error { |
| var errs []error |
| _, err := n.validatedAdvertiseAddrNMsgs() |
| errs = append(errs, err) |
| _, err = n.validatedAdvertiseAddrInterval() |
| errs = append(errs, err) |
| return errors.Join(errs...) |
| } |
| |
| func (n *Network) advertiseAddrNMsgs() (int, bool) { |
| v, err := n.validatedAdvertiseAddrNMsgs() |
| if err != nil || v == nil { |
| // On Linux, config was validated before network creation. This |
| // path is for un-set values and unsupported platforms. |
| return 0, false |
| } |
| return *v, true |
| } |
| |
| func (n *Network) advertiseAddrInterval() (time.Duration, bool) { |
| v, err := n.validatedAdvertiseAddrInterval() |
| if err != nil || v == nil { |
| // On Linux, config was validated before network creation. This |
| // path is for un-set values and unsupported platforms. |
| return 0, false |
| } |
| return *v, true |
| } |
| |
| func (n *Network) MarshalJSON() ([]byte, error) { |
| // TODO: Can be made much more generic with the help of reflection (but has some golang limitations) |
| netMap := make(map[string]any) |
| 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["enableIPv4"] = n.enableIPv4 |
| netMap["enableIPv6"] = n.enableIPv6 |
| if n.generic != nil { |
| netMap["generic"] = n.generic |
| } |
| netMap["persist"] = n.persist |
| 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 |
| netMap["skipGwAllocIPv4"] = n.skipGwAllocIPv4 |
| netMap["skipGwAllocIPv6"] = n.skipGwAllocIPv6 |
| return json.Marshal(netMap) |
| } |
| |
| func (n *Network) UnmarshalJSON(b []byte) (err error) { |
| // TODO: Can be made much more generic with the help of reflection (but has some golang limitations) |
| var netMap map[string]any |
| 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 { |
| log.G(context.TODO()).Warnf("failed to unmarshal creation time %v: %v", v, err) |
| n.created = time.Time{} |
| } |
| } |
| n.networkType = netMap["networkType"].(string) |
| n.enableIPv4 = true // Default for networks created before the option to disable IPv4 was added. |
| if v, ok := netMap["enableIPv4"]; ok { |
| n.enableIPv4 = v.(bool) |
| } |
| 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]any); 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]any); 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]any) |
| // Restore opts in their map[string]string form |
| if gv, ok := n.generic[netlabel.GenericData]; ok { |
| var lmap map[string]string |
| ba, err := json.Marshal(gv) |
| 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["ipamType"]; ok { |
| n.ipamType = v.(string) |
| } else { |
| n.ipamType = defaultipam.DriverName |
| } |
| 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) |
| } |
| if v, ok := netMap["skipGwAllocIPv4"]; ok { |
| n.skipGwAllocIPv4 = v.(bool) |
| } |
| if v, ok := netMap["skipGwAllocIPv6"]; ok { |
| n.skipGwAllocIPv6 = v.(bool) |
| } |
| 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]any) NetworkOption { |
| return func(n *Network) { |
| if n.generic == nil { |
| n.generic = make(map[string]any) |
| } |
| if val, ok := generic[netlabel.EnableIPv4]; ok { |
| n.enableIPv4 = val.(bool) |
| } |
| 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 |
| } |
| } |
| |
| // NetworkOptionEnableIPv4 returns an option setter to explicitly configure IPv4 |
| func NetworkOptionEnableIPv4(enableIPv4 bool) NetworkOption { |
| return func(n *Network) { |
| if n.generic == nil { |
| n.generic = make(map[string]any) |
| } |
| n.enableIPv4 = enableIPv4 |
| n.generic[netlabel.EnableIPv4] = enableIPv4 |
| } |
| } |
| |
| // 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]any) |
| } |
| 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]any) |
| } |
| 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 == defaultipam.DriverName { |
| 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]any) |
| } |
| 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 |
| } |
| } |
| |
| // 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, capabilities := c.drvRegistry.Driver(name) |
| if d == nil { |
| if load { |
| err := c.loadDriver(name) |
| if err != nil { |
| return nil, driverapi.Capability{}, err |
| } |
| |
| d, capabilities = 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, capabilities, nil |
| } |
| |
| func (n *Network) driverIsMultihost() bool { |
| _, capabilities, err := n.resolveDriver(n.networkType, true) |
| if err != nil { |
| return false |
| } |
| return capabilities.ConnectivityScope == scope.Global |
| } |
| |
| func (n *Network) driver(load bool) (driverapi.Driver, error) { |
| d, capabilities, 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 = capabilities.DataScope |
| } |
| if n.dynamic { |
| // If the network is dynamic, then it is swarm |
| // scoped regardless of the backing driver. |
| n.scope = scope.Swarm |
| } |
| n.mu.Unlock() |
| return d, nil |
| } |
| |
| // Delete the network. |
| 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 errdefs.NotFound(fmt.Errorf("unknown network %s id %s", name, 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} |
| } |
| |
| if !force && n.configOnly { |
| refNws := c.findNetworks(filterNetworkByConfigFrom(n.name)) |
| if len(refNws) > 0 { |
| return types.ForbiddenErrorf("configuration network %q is in use", n.Name()) |
| } |
| } |
| |
| // Check that the network is empty |
| var emptyCount int |
| if n.hasLoadBalancerEndpoint() { |
| emptyCount = 1 |
| } |
| eps := c.findEndpoints(filterEndpointByNetworkId(n.id)) |
| if !force && len(eps) > emptyCount { |
| return &ActiveEndpointsError{ |
| name: n.name, |
| id: n.id, |
| endpoints: sliceutil.Map(eps, func(ep *Endpoint) string { |
| return fmt.Sprintf(`name:%q id:%q`, ep.name, stringid.TruncateID(ep.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 |
| log.G(context.TODO()).Warnf("Error deleting load balancer sandbox: %v", err) |
| } |
| } |
| |
| // 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.storeNetwork(context.TODO(), n); err != nil { |
| return fmt.Errorf("error marking network %s (%s) for deletion: %v", n.Name(), n.ID(), err) |
| } |
| |
| 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 { |
| log.G(context.TODO()).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 |
| } |
| log.G(context.TODO()).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 |
| // |
| // TODO(robmry) - remove this once downgrade past 28.1.0 is no longer supported. |
| // The endpoint count is no longer used, it's created in the store to make |
| // downgrade work, versions older than 28.1.0 expect to read it and error if they |
| // can't. The stored count is not maintained, so the downgraded version will |
| // always find it's zero (which is usually correct because the daemon had |
| // stopped), but older daemons fix it on startup anyway. |
| if err = c.deleteFromStore(&endpointCnt{n: n}); err != nil { |
| log.G(context.TODO()).Debugf("Error deleting endpoint count from store for stale network %s (%s) for deletion: %v", n.Name(), n.ID(), err) |
| } |
| |
| if err = c.deleteStoredNetwork(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 { |
| log.G(context.TODO()).Warnf("driver error deleting network %s : %v", n.name, err) |
| } |
| } |
| |
| for _, resolver := range n.resolver { |
| resolver.Stop() |
| } |
| return nil |
| } |
| |
| func (n *Network) addEndpoint(ctx context.Context, ep *Endpoint) error { |
| d, err := n.driver(true) |
| if err != nil { |
| return fmt.Errorf("failed to add endpoint: %v", err) |
| } |
| |
| err = d.CreateEndpoint(ctx, n.id, ep.id, ep.Iface(), ep.generic) |
| if err != nil { |
| return types.InternalErrorf("failed to create endpoint %s on network %s: %v", |
| ep.Name(), n.Name(), err) |
| } |
| |
| return nil |
| } |
| |
| // CreateEndpoint creates a new endpoint to this network symbolically identified by the |
| // specified unique name. The options parameter carries driver specific options. |
| func (n *Network) CreateEndpoint(ctx context.Context, name string, options ...EndpointOption) (*Endpoint, error) { |
| var err error |
| if strings.TrimSpace(name) == "" { |
| return nil, types.InvalidParameterErrorf("invalid name: name is empty") |
| } |
| |
| 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(ctx, name, options...) |
| } |
| |
| func (n *Network) createEndpoint(ctx context.Context, name string, options ...EndpointOption) (*Endpoint, error) { |
| var err error |
| |
| ep := &Endpoint{name: name, generic: make(map[string]any), 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 { |
| log.G(ctx).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.InvalidParameterErrorf("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() |
| } |
| |
| wantIPv6 := n.enableIPv6 && !ep.disableIPv6 |
| |
| if err = ep.assignAddress(ipam, n.enableIPv4, wantIPv6); err != nil { |
| return nil, err |
| } |
| defer func() { |
| if err != nil { |
| ep.releaseAddress() |
| } |
| }() |
| |
| if err = n.addEndpoint(ctx, ep); err != nil { |
| return nil, err |
| } |
| defer func() { |
| if err != nil { |
| if e := ep.deleteEndpoint(false); e != nil { |
| log.G(ctx).Warnf("cleaning up endpoint failed %s : %v", name, e) |
| } |
| } |
| }() |
| |
| // We should perform storeEndpoint call right after addEndpoint |
| // in order to have iface properly configured |
| if err = n.getController().storeEndpoint(ctx, ep); err != nil { |
| return nil, err |
| } |
| defer func() { |
| if err != nil { |
| if e := n.getController().deleteStoredEndpoint(ep); e != nil { |
| log.G(ctx).Warnf("error rolling back endpoint %s from store: %v", name, e) |
| } |
| } |
| }() |
| |
| if !n.getController().isSwarmNode() || n.Scope() != scope.Swarm || !n.driverIsMultihost() { |
| n.updateSvcRecord(context.WithoutCancel(ctx), ep, true) |
| defer func() { |
| if err != nil { |
| n.updateSvcRecord(context.WithoutCancel(ctx), ep, false) |
| } |
| }() |
| } |
| |
| return ep, nil |
| } |
| |
| // Endpoints returns the list of Endpoint(s) in this network. |
| func (n *Network) Endpoints() []*Endpoint { |
| endpoints, err := n.getEndpointsFromStore() |
| if err != nil { |
| log.G(context.TODO()).Error(err) |
| } |
| return endpoints |
| } |
| |
| // WalkEndpoints uses the provided function to walk the Endpoints. |
| func (n *Network) WalkEndpoints(walker EndpointWalker) { |
| for _, e := range n.Endpoints() { |
| if walker(e) { |
| return |
| } |
| } |
| } |
| |
| // EndpointByName returns the Endpoint which has the passed name. If not found, |
| // an [errdefs.ErrNotFound] is returned. |
| func (n *Network) EndpointByName(name string) (*Endpoint, error) { |
| if name == "" { |
| return nil, types.InvalidParameterErrorf("invalid name: name is empty") |
| } |
| 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, errdefs.NotFound(fmt.Errorf("endpoint %s not found", name)) |
| } |
| |
| return e, nil |
| } |
| |
| // updateSvcRecord adds or deletes local DNS records for a given Endpoint. |
| func (n *Network) updateSvcRecord(ctx context.Context, ep *Endpoint, isAdd bool) { |
| ctx, span := otel.Tracer("").Start(ctx, "libnetwork.updateSvcRecord", trace.WithAttributes( |
| attribute.String("ep.name", ep.name), |
| attribute.Bool("isAdd", isAdd))) |
| defer span.End() |
| |
| iface := ep.Iface() |
| if iface == nil { |
| return |
| } |
| |
| var ipv4, ipv6 net.IP |
| if iface.Address() != nil { |
| ipv4 = iface.Address().IP |
| } |
| if iface.AddressIPv6() != nil { |
| ipv6 = iface.AddressIPv6().IP |
| } |
| |
| serviceID := ep.svcID |
| if serviceID == "" { |
| serviceID = ep.ID() |
| } |
| |
| dnsNames := ep.getDNSNames() |
| if isAdd { |
| for i, dnsName := range dnsNames { |
| ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated. |
| n.addSvcRecords(ep.ID(), dnsName, serviceID, ipv4, ipv6, ipMapUpdate, "updateSvcRecord") |
| } |
| } else { |
| for i, dnsName := range dnsNames { |
| ipMapUpdate := i == 0 // ipMapUpdate indicates whether PTR records should be updated. |
| n.deleteSvcRecords(ep.ID(), dnsName, serviceID, ipv4, ipv6, ipMapUpdate, "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, |
| }) |
| } |
| |
| // TODO(aker): remove ipMapUpdate param and add a proper method dedicated to update PTR records. |
| func (n *Network) addSvcRecords(eID, name, serviceID string, epIPv4, 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() |
| log.G(context.TODO()).Debugf("%s (%.7s).addSvcRecords(%s, %s, %s, %t) %s sid:%s", eID, networkID, name, epIPv4, 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 { |
| if epIPv4 != nil { |
| addIPToName(&sr.ipMap, name, serviceID, epIPv4) |
| } |
| if epIPv6 != nil { |
| addIPToName(&sr.ipMap, name, serviceID, epIPv6) |
| } |
| } |
| |
| if epIPv4 != nil { |
| addNameToIP(&sr.svcMap, name, serviceID, epIPv4) |
| } |
| if epIPv6 != nil { |
| addNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6) |
| } |
| } |
| |
| func (n *Network) deleteSvcRecords(eID, name, serviceID string, epIPv4, 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() |
| log.G(context.TODO()).Debugf("%s (%.7s).deleteSvcRecords(%s, %s, %s, %t) %s sid:%s ", eID, networkID, name, epIPv4, epIPv6, ipMapUpdate, method, serviceID) |
| |
| c := n.getController() |
| c.mu.Lock() |
| defer c.mu.Unlock() |
| |
| sr, ok := c.svcRecords[networkID] |
| if !ok { |
| return |
| } |
| |
| if ipMapUpdate { |
| if epIPv4 != nil { |
| delIPToName(&sr.ipMap, name, serviceID, epIPv4) |
| } |
| if epIPv6 != nil { |
| delIPToName(&sr.ipMap, name, serviceID, epIPv6) |
| } |
| } |
| |
| if epIPv4 != nil { |
| delNameToIP(&sr.svcMap, name, serviceID, epIPv4) |
| } |
| if epIPv6 != nil { |
| delNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6) |
| } |
| } |
| |
| func (n *Network) getController() *Controller { |
| n.mu.Lock() |
| defer n.mu.Unlock() |
| return n.ctrlr |
| } |
| |
| func (n *Network) ipamAllocate() (retErr 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 |
| } |
| } |
| |
| if n.enableIPv4 { |
| if err := n.ipamAllocateVersion(4, ipam); err != nil { |
| return err |
| } |
| defer func() { |
| if retErr != nil { |
| n.ipamReleaseVersion(4, ipam) |
| } |
| }() |
| } |
| |
| if n.enableIPv6 { |
| if err := n.ipamAllocateVersion(6, ipam); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func (n *Network) ipamAllocateVersion(ipVer int, ipam ipamapi.Ipam) error { |
| var ( |
| cfgList *[]*IpamConf |
| infoList *[]*IpamInfo |
| skipGwAlloc bool |
| err error |
| ) |
| |
| switch ipVer { |
| case 4: |
| cfgList = &n.ipamV4Config |
| infoList = &n.ipamV4Info |
| skipGwAlloc = n.skipGwAllocIPv4 |
| case 6: |
| cfgList = &n.ipamV6Config |
| infoList = &n.ipamV6Info |
| skipGwAlloc = n.skipGwAllocIPv6 |
| default: |
| return types.InternalErrorf("incorrect ip version passed to ipam allocate: %d", ipVer) |
| } |
| |
| if len(*cfgList) == 0 { |
| *cfgList = []*IpamConf{{}} |
| } |
| |
| *infoList = make([]*IpamInfo, len(*cfgList)) |
| |
| log.G(context.TODO()).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 |
| |
| var reserved []netip.Prefix |
| if n.Scope() != scope.Global { |
| reserved = netutils.InferReservedNetworks(ipVer == 6) |
| } |
| |
| alloc, err := ipam.RequestPool(ipamapi.PoolRequest{ |
| AddressSpace: n.addrSpace, |
| Pool: cfg.PreferredPool, |
| SubPool: cfg.SubPool, |
| Options: n.ipamOptions, |
| Exclude: reserved, |
| V6: ipVer == 6, |
| }) |
| if err != nil { |
| return err |
| } |
| |
| d.PoolID = alloc.PoolID |
| d.Pool = netiputil.ToIPNet(alloc.Pool) |
| d.Meta = alloc.Meta |
| |
| defer func() { |
| if err != nil { |
| if err := ipam.ReleasePool(d.PoolID); err != nil { |
| log.G(context.TODO()).Warnf("Failed to release address pool %s after failure to create network %s (%s)", d.PoolID, n.Name(), n.ID()) |
| } |
| } |
| }() |
| |
| // If there's no user-configured gateway address but the IPAM driver returned a gw when it |
| // set up the pool, use it. (It doesn't need to be requested/reserved in IPAM.) |
| if cfg.Gateway == "" { |
| if gws, ok := d.Meta[netlabel.Gateway]; ok { |
| if d.Gateway, err = types.ParseCIDR(gws); err != nil { |
| return types.InvalidParameterErrorf("failed to parse gateway address (%v) returned by ipam driver: %v", gws, err) |
| } |
| } |
| } |
| |
| // If there's still no gateway, reserve cfg.Gateway if the user specified it. Else, |
| // if the driver wants a gateway, let the IPAM driver select an address. |
| if d.Gateway == nil && (cfg.Gateway != "" || !skipGwAlloc) { |
| 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.InvalidParameterErrorf("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 && !errors.Is(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 { |
| log.G(context.TODO()).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: |
| log.G(context.TODO()).Warnf("incorrect ip version passed to ipam release: %d", ipVer) |
| return |
| } |
| |
| if len(*infoList) == 0 { |
| return |
| } |
| |
| log.G(context.TODO()).Debugf("releasing IPv%d pools from network %s (%s)", ipVer, n.Name(), n.ID()) |
| |
| for _, d := range *infoList { |
| if d.Gateway != nil { |
| // FIXME(robmry) - if an IPAM driver returned a gateway in Meta[netlabel.Gateway], and |
| // no user config overrode that address, it wasn't explicitly allocated so it shouldn't |
| // be released here? |
| if err := ipam.ReleaseAddress(d.PoolID, d.Gateway.IP); err != nil { |
| log.G(context.TODO()).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 && !errors.Is(err, ipamapi.ErrIPOutOfRange) { |
| log.G(context.TODO()).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 { |
| log.G(context.TODO()).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.Scope() == scope.Global { |
| return global, nil |
| } |
| return local, nil |
| } |
| |
| // 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 |
| func (n *Network) Peers() []networkdb.PeerInfo { |
| if !n.Dynamic() { |
| return []networkdb.PeerInfo{} |
| } |
| |
| a := n.getController().getAgent() |
| if a == nil { |
| return []networkdb.PeerInfo{} |
| } |
| |
| return a.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() (ipamType string, ipamOptions map[string]string, ipamV4Config []*IpamConf, ipamV6Config []*IpamConf) { |
| n.mu.Lock() |
| defer n.mu.Unlock() |
| |
| ipamV4Config = make([]*IpamConf, len(n.ipamV4Config)) |
| for i, c := range n.ipamV4Config { |
| cc := &IpamConf{} |
| if err := c.CopyTo(cc); err != nil { |
| log.G(context.TODO()).WithError(err).Error("Error copying ipam ipv4 config") |
| } |
| ipamV4Config[i] = cc |
| } |
| |
| ipamV6Config = make([]*IpamConf, len(n.ipamV6Config)) |
| for i, c := range n.ipamV6Config { |
| cc := &IpamConf{} |
| if err := c.CopyTo(cc); err != nil { |
| log.G(context.TODO()).WithError(err).Debug("Error copying ipam ipv6 config") |
| } |
| ipamV6Config[i] = cc |
| } |
| |
| return n.ipamType, n.ipamOptions, ipamV4Config, ipamV6Config |
| } |
| |
| func (n *Network) IpamInfo() (ipamV4Info []*IpamInfo, ipamV6Info []*IpamInfo) { |
| n.mu.Lock() |
| defer n.mu.Unlock() |
| |
| ipamV4Info = make([]*IpamInfo, len(n.ipamV4Info)) |
| for i, info := range n.ipamV4Info { |
| ic := &IpamInfo{} |
| if err := info.CopyTo(ic); err != nil { |
| log.G(context.TODO()).WithError(err).Error("Error copying IPv4 IPAM config") |
| } |
| ipamV4Info[i] = ic |
| } |
| |
| ipamV6Info = make([]*IpamInfo, len(n.ipamV6Info)) |
| for i, info := range n.ipamV6Info { |
| ic := &IpamInfo{} |
| if err := info.CopyTo(ic); err != nil { |
| log.G(context.TODO()).WithError(err).Error("Error copying IPv6 IPAM config") |
| } |
| ipamV6Info[i] = ic |
| } |
| |
| return ipamV4Info, ipamV6Info |
| } |
| |
| 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) IPv4Enabled() bool { |
| n.mu.Lock() |
| defer n.mu.Unlock() |
| |
| return n.enableIPv4 |
| } |
| |
| 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() |
| |
| 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 |
| } |
| |
| // ResolveName looks up addresses of ipType for name req. |
| // Returns (addresses, true) if req is found, but len(addresses) may be 0 if |
| // there are no addresses of ipType. If the name is not found, the bool return |
| // will be false. |
| func (n *Network) ResolveName(ctx context.Context, req string, ipType int) ([]net.IP, bool) { |
| c := n.getController() |
| networkID := n.ID() |
| |
| _, span := otel.Tracer("").Start(ctx, "Network.ResolveName", trace.WithAttributes( |
| attribute.String("libnet.network.name", n.Name()), |
| attribute.String("libnet.network.id", networkID), |
| )) |
| defer span.End() |
| |
| c.mu.Lock() |
| // TODO(aker): release the lock earlier |
| defer c.mu.Unlock() |
| sr, ok := c.svcRecords[networkID] |
| if !ok { |
| return nil, false |
| } |
| |
| req = strings.TrimSuffix(req, ".") |
| req = strings.ToLower(req) |
| |
| ipSet, ok4 := sr.svcMap.Get(req) |
| ipSet6, ok6 := sr.svcIPv6Map.Get(req) |
| if !ok4 && !ok6 { |
| // No result for v4 or v6, the name doesn't exist. |
| return nil, false |
| } |
| if ipType == types.IPv6 { |
| ipSet = ipSet6 |
| } |
| |
| // 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, true |
| } |
| |
| 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(_ context.Context, 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 stabilization |
| elem := elemSet[0] |
| if elem.extResolver { |
| return "" |
| } |
| |
| return elem.name + "." + nwName |
| } |
| |
| func (n *Network) ResolveService(ctx context.Context, name string) ([]*net.SRV, []net.IP) { |
| c := n.getController() |
| |
| srv := []*net.SRV{} |
| ip := []net.IP{} |
| |
| log.G(ctx).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) NdotsSet() bool { |
| return false |
| } |
| |
| // config-only network is looked up by name |
| func (c *Controller) getConfigNetwork(name string) (*Network, error) { |
| var n *Network |
| c.WalkNetworks(func(current *Network) bool { |
| if current.ConfigOnly() && current.Name() == name { |
| n = current |
| return true |
| } |
| return false |
| }) |
| |
| if n == nil { |
| return nil, types.NotFoundErrorf("configuration network %q not found", name) |
| } |
| |
| return n, 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(context.TODO(), sandboxName, sbOptions...) |
| if err != nil { |
| return err |
| } |
| defer func() { |
| if retErr != nil { |
| if e := n.ctrlr.SandboxDestroy(context.WithoutCancel(context.TODO()), sandboxName); e != nil { |
| log.G(context.TODO()).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(), |
| } |
| ep, err := n.createEndpoint(context.TODO(), endpointName, epOptions...) |
| if err != nil { |
| return err |
| } |
| defer func() { |
| if retErr != nil { |
| if e := ep.Delete(context.WithoutCancel(context.TODO()), true); e != nil { |
| log.G(context.TODO()).Warnf("could not delete endpoint %s on failure on failure (%v): %v", endpointName, retErr, e) |
| } |
| } |
| }() |
| |
| if err := ep.Join(context.TODO(), 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 { |
| log.G(context.TODO()).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 { |
| log.G(context.TODO()).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(context.TODO(), true); err != nil { |
| log.G(context.TODO()).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(context.TODO(), sandboxName); err != nil { |
| return fmt.Errorf("Failed to delete %s sandbox: %v", sandboxName, err) |
| } |
| return nil |
| } |