@@ -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 {
468469func (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
@@ -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+
9551036func (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
13071388func (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