Skip to content

Commit 82d3319

Browse files
committedJun 29, 2017
Update network containers in refresh loop.
At some point, newer Docker versions stopped reporting container information on network ls. This caused a bug where the refresh loop was unable to get full network information (in particular container information). This commit fixes that by manually iterating over all containers and extracting network information from them. Signed-off-by: Nishant Totla <[email protected]>
1 parent e15a2ea commit 82d3319

File tree

3 files changed

+93
-5
lines changed

3 files changed

+93
-5
lines changed
 

‎api/handlers.go

+2
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,8 @@ func proxyNetworkConnect(c *context, w http.ResponseWriter, r *http.Request) {
11861186
cb := func(resp *http.Response) {
11871187
// force fresh networks on this engine
11881188
container.Engine.RefreshNetworks()
1189+
// force refresh this container so that it is up to date in the cache
1190+
container.Engine.UpdateNetworkContainers(container.ID, true)
11891191
}
11901192

11911193
// request is forwarded to the container's address

‎cluster/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type ContainerConfig struct {
2323

2424
// OldContainerConfig contains additional fields for backward compatibility
2525
// This should be removed after we stop supporting API versions <= 1.8
26+
// TODO(nishanttotla): Remove this field
2627
type OldContainerConfig struct {
2728
ContainerConfig
2829
Memory int64

‎cluster/engine.go

+90-5
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"net"
1414
"net/http"
1515
"net/url"
16+
"strconv"
1617
"strings"
1718
"sync"
1819
"time"
@@ -468,7 +469,7 @@ func (e *Engine) EngineToContainerNode() *types.ContainerNode {
468469
func (e *Engine) updateClientVersionFromServer(serverVersion string) {
469470
// v will be >= 1.8, since this is checked earlier
470471
// for server/API version reference, check https://docs.docker.com/engine/api/
471-
// new versions of Docker look like 17.06-ce etc.
472+
// new versions of Docker look like 17.06.x-ce etc.
472473
s := strings.Split(serverVersion, "-")
473474
serverVersion = s[0]
474475

@@ -485,7 +486,7 @@ func (e *Engine) updateClientVersionFromServer(serverVersion string) {
485486
e.apiClient.UpdateClientVersion("1.24")
486487
case versions.LessThan(serverVersion, "1.13.1"):
487488
e.apiClient.UpdateClientVersion("1.25")
488-
case versions.LessThan(serverVersion, "17.03.1") || serverVersion == "1.13.1":
489+
case versions.LessThan(serverVersion, "17.03.1") || versions.Equal(serverVersion, "1.13.1"):
489490
e.apiClient.UpdateClientVersion("1.26")
490491
case versions.LessThan(serverVersion, "17.04"):
491492
e.apiClient.UpdateClientVersion("1.27")
@@ -945,13 +946,93 @@ func (e *Engine) refreshLoop() {
945946
e.RefreshVolumes()
946947
e.RefreshNetworks()
947948
e.RefreshImages()
948-
log.WithFields(log.Fields{"id": e.ID, "name": e.Name}).Debugf("Engine update succeeded")
949+
err := e.UpdateNetworkContainers("", false)
950+
if err != nil {
951+
log.WithFields(log.Fields{"id": e.ID, "name": e.Name}).Debugf("Engine refresh succeeded, but network containers update failed: %s", err.Error())
952+
} else {
953+
log.WithFields(log.Fields{"id": e.ID, "name": e.Name}).Debugf("Engine update succeeded")
954+
}
949955
} else {
950956
log.WithFields(log.Fields{"id": e.ID, "name": e.Name}).Debugf("Engine refresh failed")
951957
}
952958
}
953959
}
954960

961+
// UpdateNetworkContainers updates the list of containers attached to each network.
962+
// This is required because the RefreshNetworks uses NetworkList which has stopped
963+
// returning this information for recent API versions. Note that the container cache
964+
// is used to obtain this information, which means that if the container cache isn't
965+
// up to date for whatever reason, then the network information might be out of date
966+
// as well. This needs to be considered while designing networking related functionality.
967+
// Ideally, this function should only be used in the refresh loop, and called AFTER
968+
// RefreshNetworks() because RefreshNetworks() will not populate network information
969+
// correctly if container refresh hasn't taken place
970+
971+
// The containerID argument can be provided along with a full argument can be used to
972+
// limit the update as it concerns only one container instead of doing it for all
973+
// containers. The idea is that sometimes only things about one container may have
974+
// changed, and we don't need to run the loop over all containers.
975+
func (e *Engine) UpdateNetworkContainers(containerID string, full bool) error {
976+
containerMap := make(map[string]*Container)
977+
if containerID != "" {
978+
ctr, err := e.refreshContainer(containerID, full)
979+
if err != nil {
980+
return err
981+
}
982+
containerMap[containerID] = ctr
983+
}
984+
985+
e.Lock()
986+
defer e.Unlock()
987+
988+
// if a specific container is not being targeted, then an update for all containers
989+
// will take place
990+
if containerID == "" {
991+
containerMap = e.containers
992+
}
993+
994+
for _, c := range containerMap {
995+
if c.NetworkSettings == nil {
996+
continue
997+
}
998+
for _, n := range c.NetworkSettings.Networks {
999+
if engineNetwork, ok := e.networks[n.NetworkID]; ok {
1000+
// extract container name
1001+
ctrName := ""
1002+
if len(c.Names) != 0 {
1003+
if s := strings.Split(c.Names[0], "/"); len(s) > 1 {
1004+
ctrName = s[1]
1005+
} else {
1006+
ctrName = s[0]
1007+
}
1008+
}
1009+
// extract ip addresses
1010+
ipv4address := ""
1011+
ipv6address := ""
1012+
if n.IPAddress != "" {
1013+
ipv4address = n.IPAddress + "/" + strconv.Itoa(n.IPPrefixLen)
1014+
}
1015+
if n.GlobalIPv6Address != "" {
1016+
ipv6address = n.GlobalIPv6Address + "/" + strconv.Itoa(n.GlobalIPv6PrefixLen)
1017+
}
1018+
// udpate network information
1019+
engineNetwork.Containers[c.ID] = types.EndpointResource{
1020+
Name: ctrName,
1021+
EndpointID: n.EndpointID,
1022+
MacAddress: n.MacAddress,
1023+
IPv4Address: ipv4address,
1024+
IPv6Address: ipv6address,
1025+
}
1026+
} else {
1027+
// it shouldn't be the case that a network which a container is connected to wasn't
1028+
// even listed. Return an error when that happens.
1029+
return fmt.Errorf("container %s connected to network %s but the network wasn't listed in the refresh loop", c.ID, n.NetworkID)
1030+
}
1031+
}
1032+
}
1033+
return nil
1034+
}
1035+
9551036
func (e *Engine) emitEvent(event string) {
9561037
// If there is no event handler registered, abort right now.
9571038
if e.eventHandler == nil {
@@ -1306,6 +1387,7 @@ func (e *Engine) String() string {
13061387

13071388
func (e *Engine) handler(msg events.Message) error {
13081389
// Something changed - refresh our internal state.
1390+
// TODO(nishanttotla): Possibly container events should trigger a container refresh
13091391

13101392
switch msg.Type {
13111393
case "network":
@@ -1531,8 +1613,11 @@ func (e *Engine) NetworkDisconnect(container *Container, network string, force b
15311613
if err != nil {
15321614
return err
15331615
}
1534-
1535-
return e.RefreshNetworks()
1616+
err = e.RefreshNetworks()
1617+
if err != nil {
1618+
return err
1619+
}
1620+
return e.UpdateNetworkContainers(container.ID, true)
15361621
}
15371622

15381623
//IsConnectionError returns true when err is connection problem

0 commit comments

Comments
 (0)