diff --git a/libnetwork/api/api.go b/libnetwork/api/api.go index 95accac2c3..083eb4205f 100644 --- a/libnetwork/api/api.go +++ b/libnetwork/api/api.go @@ -379,6 +379,10 @@ func procCreateEndpoint(c libnetwork.NetworkController, vars map[string]string, setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(ec.PortMapping)) } + for _, str := range ec.MyAliases { + setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str)) + } + ep, err := n.CreateEndpoint(ec.Name, setFctList...) if err != nil { return "", convertNetworkError(err) @@ -624,6 +628,10 @@ func procPublishService(c libnetwork.NetworkController, vars map[string]string, setFctList = append(setFctList, libnetwork.CreateOptionPortMapping(sp.PortMapping)) } + for _, str := range sp.MyAliases { + setFctList = append(setFctList, libnetwork.CreateOptionMyAlias(str)) + } + ep, err := n.CreateEndpoint(sp.Name, setFctList...) if err != nil { return "", endpointToService(convertNetworkError(err)) diff --git a/libnetwork/api/types.go b/libnetwork/api/types.go index bd37b0c5a2..68db1edffc 100644 --- a/libnetwork/api/types.go +++ b/libnetwork/api/types.go @@ -43,6 +43,7 @@ type networkCreate struct { // endpointCreate represents the body of the "create endpoint" http request message type endpointCreate struct { Name string `json:"name"` + MyAliases []string `json:"my_aliases"` ExposedPorts []types.TransportPort `json:"exposed_ports"` PortMapping []types.PortBinding `json:"port_mapping"` } @@ -69,6 +70,7 @@ type endpointJoin struct { // servicePublish represents the body of the "publish service" http request message type servicePublish struct { Name string `json:"name"` + MyAliases []string `json:"my_aliases"` Network string `json:"network_name"` ExposedPorts []types.TransportPort `json:"exposed_ports"` PortMapping []types.PortBinding `json:"port_mapping"` diff --git a/libnetwork/client/service.go b/libnetwork/client/service.go index 16acab6d5f..fb70228485 100644 --- a/libnetwork/client/service.go +++ b/libnetwork/client/service.go @@ -163,6 +163,8 @@ func parseServiceName(name string) (string, string) { // CmdServicePublish handles service create UI func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error { cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false) + flAlias := opts.NewListOpts(netutils.ValidateAlias) + cmd.Var(&flAlias, []string{"-alias"}, "Add alias to self") cmd.Require(flag.Exact, 1) err := cmd.ParseFlags(args, true) if err != nil { @@ -170,7 +172,7 @@ func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error { } sn, nn := parseServiceName(cmd.Arg(0)) - sc := serviceCreate{Name: sn, Network: nn} + sc := serviceCreate{Name: sn, Network: nn, MyAliases: flAlias.GetAll()} obj, _, err := readBody(cli.call("POST", "/services", sc, nil)) if err != nil { return err diff --git a/libnetwork/client/types.go b/libnetwork/client/types.go index a18d16ee3b..1337d60efc 100644 --- a/libnetwork/client/types.go +++ b/libnetwork/client/types.go @@ -43,6 +43,7 @@ type networkCreate struct { // serviceCreate represents the body of the "publish service" http request message type serviceCreate struct { Name string `json:"name"` + MyAliases []string `json:"my_aliases"` Network string `json:"network_name"` ExposedPorts []types.TransportPort `json:"exposed_ports"` PortMapping []types.PortBinding `json:"port_mapping"` diff --git a/libnetwork/endpoint.go b/libnetwork/endpoint.go index bf76fa83f2..de08c4210f 100644 --- a/libnetwork/endpoint.go +++ b/libnetwork/endpoint.go @@ -65,6 +65,7 @@ type endpoint struct { prefAddressV6 net.IP ipamOptions map[string]string aliases map[string]string + myAliases []string dbIndex uint64 dbExists bool sync.Mutex @@ -85,6 +86,7 @@ func (ep *endpoint) MarshalJSON() ([]byte, error) { epMap["sandbox"] = ep.sandboxID epMap["anonymous"] = ep.anonymous epMap["disableResolution"] = ep.disableResolution + epMap["myAliases"] = ep.myAliases return json.Marshal(epMap) } @@ -165,6 +167,10 @@ func (ep *endpoint) UnmarshalJSON(b []byte) (err error) { if v, ok := epMap["disableResolution"]; ok { ep.disableResolution = v.(bool) } + ma, _ := json.Marshal(epMap["myAliases"]) + var myAliases []string + json.Unmarshal(ma, &myAliases) + ep.myAliases = myAliases return nil } @@ -193,6 +199,9 @@ func (ep *endpoint) CopyTo(o datastore.KVObject) error { dstEp.exposedPorts = make([]types.TransportPort, len(ep.exposedPorts)) copy(dstEp.exposedPorts, ep.exposedPorts) + dstEp.myAliases = make([]string, len(ep.myAliases)) + copy(dstEp.myAliases, ep.myAliases) + dstEp.generic = options.Generic{} for k, v := range ep.generic { dstEp.generic[k] = v @@ -215,6 +224,13 @@ func (ep *endpoint) Name() string { return ep.name } +func (ep *endpoint) MyAliases() []string { + ep.Lock() + defer ep.Unlock() + + return ep.myAliases +} + func (ep *endpoint) Network() string { if ep.network == nil { return "" @@ -759,6 +775,13 @@ func CreateOptionAlias(name string, alias string) EndpointOption { } } +//CreateOptionMyAlias function returns an option setter for setting endpoint's self alias +func CreateOptionMyAlias(alias string) EndpointOption { + return func(ep *endpoint) { + ep.myAliases = append(ep.myAliases, alias) + } +} + // JoinOptionPriority function returns an option setter for priority option to // be passed to the endpoint.Join() method. func JoinOptionPriority(ep Endpoint, prio int) EndpointOption { diff --git a/libnetwork/network.go b/libnetwork/network.go index 45626df896..2fe49062f0 100644 --- a/libnetwork/network.go +++ b/libnetwork/network.go @@ -70,7 +70,7 @@ type NetworkInfo interface { type EndpointWalker func(ep Endpoint) bool type svcInfo struct { - svcMap map[string]net.IP + svcMap map[string][]net.IP ipMap map[string]string } @@ -825,36 +825,77 @@ func (n *network) updateSvcRecord(ep *endpoint, localEps []*endpoint, isAdd bool return } + epName := ep.Name() + if iface := ep.Iface(); iface.Address() != nil { + myAliases := ep.MyAliases() + if isAdd { + n.addSvcRecords(epName, iface.Address().IP, true) + for _, alias := range myAliases { + n.addSvcRecords(alias, iface.Address().IP, false) + } + } else { + n.deleteSvcRecords(epName, iface.Address().IP, true) + for _, alias := range myAliases { + n.deleteSvcRecords(alias, iface.Address().IP, false) + } + } + } +} + +func (n *network) addSvcRecords(name string, epIP net.IP, ipMapUpdate bool) { c := n.getController() + c.Lock() + defer c.Unlock() sr, ok := c.svcDb[n.ID()] if !ok { - c.svcDb[n.ID()] = svcInfo{ - svcMap: make(map[string]net.IP), + sr = svcInfo{ + svcMap: make(map[string][]net.IP), ipMap: make(map[string]string), } - sr = c.svcDb[n.ID()] + c.svcDb[n.ID()] = sr } - epName := ep.Name() - n.Lock() - if iface := ep.Iface(); iface.Address() != nil { - - reverseIP := netutils.ReverseIP(iface.Address().IP.String()) - if isAdd { - // If we already have this endpoint in service db just return - if _, ok := sr.svcMap[epName]; ok { - n.Unlock() - return - } - - sr.svcMap[epName] = iface.Address().IP - sr.ipMap[reverseIP] = epName - } else { - delete(sr.svcMap, epName) - delete(sr.ipMap, reverseIP) + if ipMapUpdate { + reverseIP := netutils.ReverseIP(epIP.String()) + if _, ok := sr.ipMap[reverseIP]; !ok { + sr.ipMap[reverseIP] = name } } - n.Unlock() + + ipList := sr.svcMap[name] + for _, ip := range ipList { + if ip.Equal(epIP) { + return + } + } + sr.svcMap[name] = append(sr.svcMap[name], epIP) +} + +func (n *network) deleteSvcRecords(name string, epIP net.IP, ipMapUpdate bool) { + c := n.getController() + c.Lock() + defer c.Unlock() + sr, ok := c.svcDb[n.ID()] + if !ok { + return + } + + if ipMapUpdate { + delete(sr.ipMap, netutils.ReverseIP(epIP.String())) + } + + ipList := sr.svcMap[name] + for i, ip := range ipList { + if ip.Equal(epIP) { + ipList = append(ipList[:i], ipList[i+1:]...) + break + } + } + sr.svcMap[name] = ipList + + if len(ipList) == 0 { + delete(sr.svcMap, name) + } } func (n *network) getSvcRecords(ep *endpoint) []etchosts.Record { @@ -871,7 +912,7 @@ func (n *network) getSvcRecords(ep *endpoint) []etchosts.Record { recs = append(recs, etchosts.Record{ Hosts: h, - IP: ip.String(), + IP: ip[0].String(), }) } diff --git a/libnetwork/sandbox.go b/libnetwork/sandbox.go index 9f5f54b2ff..2f69897d12 100644 --- a/libnetwork/sandbox.go +++ b/libnetwork/sandbox.go @@ -481,7 +481,7 @@ func (sb *sandbox) resolveName(req string, networkName string, epList []*endpoin ip, ok := sr.svcMap[name] n.Unlock() if ok { - return ip + return ip[0] } } return nil diff --git a/libnetwork/test/integration/dnet/bridge.bats b/libnetwork/test/integration/dnet/bridge.bats index e3be64dccb..a3a852ce54 100644 --- a/libnetwork/test/integration/dnet/bridge.bats +++ b/libnetwork/test/integration/dnet/bridge.bats @@ -249,3 +249,33 @@ function test_single_network_connectivity() { dnet_cmd $(inst_id2port 1) container rm container_2 dnet_cmd $(inst_id2port 1) network rm br1 } + +@test "Test bridge network global alias support" { + skip_for_circleci + dnet_cmd $(inst_id2port 1) network create -d bridge br1 + dnet_cmd $(inst_id2port 1) network create -d bridge br2 + dnet_cmd $(inst_id2port 1) container create container_1 + net_connect 1 container_1 br1 : c1 + dnet_cmd $(inst_id2port 1) container create container_2 + net_connect 1 container_2 br1 : shared + dnet_cmd $(inst_id2port 1) container create container_3 + net_connect 1 container_3 br1 : shared + + runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_2) "ping -c 1 container_1" + runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_2) "ping -c 1 c1" + runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 container_2" + runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 shared" + + net_disconnect 1 container_2 br1 + dnet_cmd $(inst_id2port 1) container rm container_2 + + runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 container_3" + runc $(dnet_container_name 1 bridge) $(get_sbox_id 1 container_1) "ping -c 1 shared" + + net_disconnect 1 container_1 br1 + dnet_cmd $(inst_id2port 1) container rm container_1 + net_disconnect 1 container_3 br1 + dnet_cmd $(inst_id2port 1) container rm container_3 + + dnet_cmd $(inst_id2port 1) network rm br1 +} diff --git a/libnetwork/test/integration/dnet/helpers.bash b/libnetwork/test/integration/dnet/helpers.bash index b12eb91d3f..5ef91b4c5e 100644 --- a/libnetwork/test/integration/dnet/helpers.bash +++ b/libnetwork/test/integration/dnet/helpers.bash @@ -18,12 +18,17 @@ function get_sbox_id() { } function net_connect() { - local al + local al gl if [ -n "$4" ]; then - al="--alias=${4}" + if [ "${4}" != ":" ]; then + al="--alias=${4}" + fi fi - dnet_cmd $(inst_id2port ${1}) service publish ${2}.${3} - dnet_cmd $(inst_id2port ${1}) service attach $al ${2} ${2}.${3} + if [ -n "$5" ]; then + gl="--alias=${5}" + fi + dnet_cmd $(inst_id2port ${1}) service publish $gl ${2}.${3} + dnet_cmd $(inst_id2port ${1}) service attach $al ${2} ${2}.${3} } function net_disconnect() {