From 67086764647b1c16553d1dd057f0e1d2c775c625 Mon Sep 17 00:00:00 2001 From: Santhosh Manohar Date: Sat, 11 Mar 2017 17:22:15 -0800 Subject: [PATCH 1/2] Vendor libnetwork for network inspect --verbose changes Signed-off-by: Santhosh Manohar --- vendor.conf | 2 +- vendor/github.com/docker/libnetwork/agent.go | 121 +++++++++++++++++- .../docker/libnetwork/driverapi/driverapi.go | 37 +++++- .../libnetwork/drivers/bridge/bridge.go | 4 + .../drivers/bridge/setup_ip_tables.go | 6 - .../docker/libnetwork/drivers/host/host.go | 4 + .../libnetwork/drivers/ipvlan/ipvlan.go | 4 + .../libnetwork/drivers/macvlan/macvlan.go | 4 + .../docker/libnetwork/drivers/null/null.go | 4 + .../libnetwork/drivers/overlay/encryption.go | 70 +++++++--- .../libnetwork/drivers/overlay/joinleave.go | 17 +++ .../libnetwork/drivers/overlay/ov_network.go | 2 +- .../drivers/overlay/ovmanager/ovmanager.go | 4 + .../libnetwork/drivers/remote/driver.go | 4 + .../drivers/solaris/bridge/bridge.go | 4 + .../drivers/solaris/overlay/joinleave.go | 4 + .../drivers/solaris/overlay/ov_network.go | 2 +- .../windows/overlay/joinleave_windows.go | 4 + .../windows/overlay/ov_network_windows.go | 2 +- .../libnetwork/drivers/windows/windows.go | 4 + .../docker/libnetwork/iptables/iptables.go | 71 +++++++--- .../github.com/docker/libnetwork/network.go | 23 +++- .../docker/libnetwork/networkdb/networkdb.go | 16 +++ .../docker/libnetwork/service_common.go | 20 +++ 24 files changed, 377 insertions(+), 56 deletions(-) diff --git a/vendor.conf b/vendor.conf index e06832e875..b3484fbd42 100644 --- a/vendor.conf +++ b/vendor.conf @@ -23,7 +23,7 @@ github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 github.com/imdario/mergo 0.2.1 #get libnetwork packages -github.com/docker/libnetwork 1a019214c9cb80bd56219e5d6994a22caf302895 +github.com/docker/libnetwork 4610dd67c7b9828bb4719d8aa2ac53a7f1f739d2 github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec diff --git a/vendor/github.com/docker/libnetwork/agent.go b/vendor/github.com/docker/libnetwork/agent.go index b0c65cba98..feda2c2846 100644 --- a/vendor/github.com/docker/libnetwork/agent.go +++ b/vendor/github.com/docker/libnetwork/agent.go @@ -44,6 +44,8 @@ type agent struct { sync.Mutex } +const libnetworkEPTable = "endpoint_table" + func getBindAddr(ifaceName string) (string, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { @@ -285,7 +287,7 @@ func (c *controller) agentInit(listenAddr, bindAddrOrInterface, advertiseAddr st return err } - ch, cancel := nDB.Watch("endpoint_table", "", "") + ch, cancel := nDB.Watch(libnetworkEPTable, "", "") nodeCh, cancel := nDB.Watch(networkdb.NodeTable, "", "") c.Lock() @@ -385,6 +387,111 @@ func (c *controller) agentClose() { agent.networkDB.Close() } +// Task has the backend container details +type Task struct { + Name string + EndpointID string + EndpointIP string + Info map[string]string +} + +// ServiceInfo has service specific details along with the list of backend tasks +type ServiceInfo struct { + VIP string + LocalLBIndex int + Tasks []Task + Ports []string +} + +type epRecord struct { + ep EndpointRecord + info map[string]string + lbIndex int +} + +func (n *network) Services() map[string]ServiceInfo { + eps := make(map[string]epRecord) + + if !n.isClusterEligible() { + return nil + } + agent := n.getController().getAgent() + if agent == nil { + return nil + } + + // Walk through libnetworkEPTable and fetch the driver agnostic endpoint info + entries := agent.networkDB.GetTableByNetwork(libnetworkEPTable, n.id) + for eid, value := range entries { + var epRec EndpointRecord + nid := n.ID() + if err := proto.Unmarshal(value.([]byte), &epRec); err != nil { + logrus.Errorf("Unmarshal of libnetworkEPTable failed for endpoint %s in network %s, %v", eid, nid, err) + continue + } + i := n.getController().getLBIndex(epRec.ServiceID, nid, epRec.IngressPorts) + eps[eid] = epRecord{ + ep: epRec, + lbIndex: i, + } + } + + // Walk through the driver's tables, have the driver decode the entries + // and return the tuple {ep ID, value}. value is a string that coveys + // relevant info about the endpoint. + d, err := n.driver(true) + if err != nil { + logrus.Errorf("Could not resolve driver for network %s/%s while fetching services: %v", n.networkType, n.ID(), err) + return nil + } + for _, table := range n.driverTables { + if table.objType != driverapi.EndpointObject { + continue + } + entries := agent.networkDB.GetTableByNetwork(table.name, n.id) + for key, value := range entries { + epID, info := d.DecodeTableEntry(table.name, key, value.([]byte)) + if ep, ok := eps[epID]; !ok { + logrus.Errorf("Inconsistent driver and libnetwork state for endpoint %s", epID) + } else { + ep.info = info + eps[epID] = ep + } + } + } + + // group the endpoints into a map keyed by the service name + sinfo := make(map[string]ServiceInfo) + for ep, epr := range eps { + var ( + s ServiceInfo + ok bool + ) + if s, ok = sinfo[epr.ep.ServiceName]; !ok { + s = ServiceInfo{ + VIP: epr.ep.VirtualIP, + LocalLBIndex: epr.lbIndex, + } + } + ports := []string{} + if s.Ports == nil { + for _, port := range epr.ep.IngressPorts { + p := fmt.Sprintf("Target: %d, Publish: %d", port.TargetPort, port.PublishedPort) + ports = append(ports, p) + } + s.Ports = ports + } + s.Tasks = append(s.Tasks, Task{ + Name: epr.ep.Name, + EndpointID: ep, + EndpointIP: epr.ep.EndpointIP, + Info: epr.info, + }) + sinfo[epr.ep.ServiceName] = s + } + return sinfo +} + func (n *network) isClusterEligible() bool { if n.driverScope() != datastore.GlobalScope { return false @@ -508,7 +615,7 @@ func (ep *endpoint) addServiceInfoToCluster() error { } if agent != nil { - if err := agent.networkDB.CreateEntry("endpoint_table", n.ID(), ep.ID(), buf); err != nil { + if err := agent.networkDB.CreateEntry(libnetworkEPTable, n.ID(), ep.ID(), buf); err != nil { return err } } @@ -541,7 +648,7 @@ func (ep *endpoint) deleteServiceInfoFromCluster() error { } if agent != nil { - if err := agent.networkDB.DeleteEntry("endpoint_table", n.ID(), ep.ID()); err != nil { + if err := agent.networkDB.DeleteEntry(libnetworkEPTable, n.ID(), ep.ID()); err != nil { return err } } @@ -559,8 +666,8 @@ func (n *network) addDriverWatches() { if agent == nil { return } - for _, tableName := range n.driverTables { - ch, cancel := agent.networkDB.Watch(tableName, n.ID(), "") + for _, table := range n.driverTables { + ch, cancel := agent.networkDB.Watch(table.name, n.ID(), "") agent.Lock() agent.driverCancelFuncs[n.ID()] = append(agent.driverCancelFuncs[n.ID()], cancel) agent.Unlock() @@ -571,9 +678,9 @@ func (n *network) addDriverWatches() { return } - agent.networkDB.WalkTable(tableName, func(nid, key string, value []byte) bool { + agent.networkDB.WalkTable(table.name, func(nid, key string, value []byte) bool { if nid == n.ID() { - d.EventNotify(driverapi.Create, nid, tableName, key, value) + d.EventNotify(driverapi.Create, nid, table.name, key, value) } return false diff --git a/vendor/github.com/docker/libnetwork/driverapi/driverapi.go b/vendor/github.com/docker/libnetwork/driverapi/driverapi.go index 7fe6f611a4..074438ef88 100644 --- a/vendor/github.com/docker/libnetwork/driverapi/driverapi.go +++ b/vendor/github.com/docker/libnetwork/driverapi/driverapi.go @@ -72,6 +72,16 @@ type Driver interface { // only invoked for the global scope driver. EventNotify(event EventType, nid string, tableName string, key string, value []byte) + // DecodeTableEntry passes the driver a key, value pair from table it registered + // with libnetwork. Driver should return {object ID, map[string]string} tuple. + // If DecodeTableEntry is called for a table associated with NetworkObject or + // EndpointObject the return object ID should be the network id or endppoint id + // associated with that entry. map should have information about the object that + // can be presented to the user. + // For exampe: overlay driver returns the VTEP IP of the host that has the endpoint + // which is shown in 'network inspect --verbose' + DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) + // Type returns the type of this driver, the network type this driver manages Type() string @@ -84,7 +94,7 @@ type Driver interface { type NetworkInfo interface { // TableEventRegister registers driver interest in a given // table name. - TableEventRegister(tableName string) error + TableEventRegister(tableName string, objType ObjectType) error } // InterfaceInfo provides a go interface for drivers to retrive @@ -175,3 +185,28 @@ const ( // Delete event is generated when a table entry is deleted. Delete ) + +// ObjectType represents the type of object driver wants to store in libnetwork's networkDB +type ObjectType int + +const ( + // EndpointObject should be set for libnetwork endpoint object related data + EndpointObject ObjectType = 1 + iota + // NetworkObject should be set for libnetwork network object related data + NetworkObject + // OpaqueObject is for driver specific data with no corresponding libnetwork object + OpaqueObject +) + +// IsValidType validates the passed in type against the valid object types +func IsValidType(objType ObjectType) bool { + switch objType { + case EndpointObject: + fallthrough + case NetworkObject: + fallthrough + case OpaqueObject: + return true + } + return false +} diff --git a/vendor/github.com/docker/libnetwork/drivers/bridge/bridge.go b/vendor/github.com/docker/libnetwork/drivers/bridge/bridge.go index fa051bde5a..13446f82ea 100644 --- a/vendor/github.com/docker/libnetwork/drivers/bridge/bridge.go +++ b/vendor/github.com/docker/libnetwork/drivers/bridge/bridge.go @@ -575,6 +575,10 @@ func (d *driver) NetworkFree(id string) error { func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + // Create a new network using bridge plugin func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error { if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" { diff --git a/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go b/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go index 1ac7fbc808..b2720c54f7 100644 --- a/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go +++ b/vendor/github.com/docker/libnetwork/drivers/bridge/setup_ip_tables.go @@ -140,7 +140,6 @@ func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairp hpNatRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}} skipDNAT = iptRule{table: iptables.Nat, chain: DockerChain, preArgs: []string{"-t", "nat"}, args: []string{"-i", bridgeIface, "-j", "RETURN"}} outRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}} - inRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}} ) // Set NAT. @@ -173,11 +172,6 @@ func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairp return err } - // Set Accept on incoming packets for existing connections. - if err := programChainRule(inRule, "ACCEPT INCOMING", enable); err != nil { - return err - } - return nil } diff --git a/vendor/github.com/docker/libnetwork/drivers/host/host.go b/vendor/github.com/docker/libnetwork/drivers/host/host.go index 3bc9099761..7b4a986e6c 100644 --- a/vendor/github.com/docker/libnetwork/drivers/host/host.go +++ b/vendor/github.com/docker/libnetwork/drivers/host/host.go @@ -35,6 +35,10 @@ func (d *driver) NetworkFree(id string) error { func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error { d.Lock() defer d.Unlock() diff --git a/vendor/github.com/docker/libnetwork/drivers/ipvlan/ipvlan.go b/vendor/github.com/docker/libnetwork/drivers/ipvlan/ipvlan.go index cd0c830f79..296804dc1a 100644 --- a/vendor/github.com/docker/libnetwork/drivers/ipvlan/ipvlan.go +++ b/vendor/github.com/docker/libnetwork/drivers/ipvlan/ipvlan.go @@ -108,3 +108,7 @@ func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{ func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } + +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} diff --git a/vendor/github.com/docker/libnetwork/drivers/macvlan/macvlan.go b/vendor/github.com/docker/libnetwork/drivers/macvlan/macvlan.go index 23fa850edc..49b9fbae00 100644 --- a/vendor/github.com/docker/libnetwork/drivers/macvlan/macvlan.go +++ b/vendor/github.com/docker/libnetwork/drivers/macvlan/macvlan.go @@ -110,3 +110,7 @@ func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{ func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } + +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} diff --git a/vendor/github.com/docker/libnetwork/drivers/null/null.go b/vendor/github.com/docker/libnetwork/drivers/null/null.go index 03f9777040..7f2a5e32f7 100644 --- a/vendor/github.com/docker/libnetwork/drivers/null/null.go +++ b/vendor/github.com/docker/libnetwork/drivers/null/null.go @@ -35,6 +35,10 @@ func (d *driver) NetworkFree(id string) error { func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error { d.Lock() defer d.Unlock() diff --git a/vendor/github.com/docker/libnetwork/drivers/overlay/encryption.go b/vendor/github.com/docker/libnetwork/drivers/overlay/encryption.go index b4c3aade49..1d59f238b0 100644 --- a/vendor/github.com/docker/libnetwork/drivers/overlay/encryption.go +++ b/vendor/github.com/docker/libnetwork/drivers/overlay/encryption.go @@ -20,7 +20,7 @@ import ( ) const ( - mark = uint32(0xD0C4E3) + r = 0xD0C4E3 timeout = 30 pktExpansion = 26 // SPI(4) + SeqN(4) + IV(8) + PadLength(1) + NextHeader(1) + ICV(8) ) @@ -31,6 +31,8 @@ const ( bidir ) +var spMark = netlink.XfrmMark{Value: uint32(r), Mask: 0xffffffff} + type key struct { value []byte tag uint32 @@ -201,7 +203,7 @@ func programMangle(vni uint32, add bool) (err error) { var ( p = strconv.FormatUint(uint64(vxlanPort), 10) c = fmt.Sprintf("0>>22&0x3C@12&0xFFFFFF00=%d", int(vni)<<8) - m = strconv.FormatUint(uint64(mark), 10) + m = strconv.FormatUint(uint64(r), 10) chain = "OUTPUT" rule = []string{"-p", "udp", "--dport", p, "-m", "u32", "--u32", c, "-j", "MARK", "--set-mark", m} a = "-A" @@ -271,6 +273,7 @@ func programSA(localIP, remoteIP net.IP, spi *spi, k *key, dir int, add bool) (f Proto: netlink.XFRM_PROTO_ESP, Spi: spi.reverse, Mode: netlink.XFRM_MODE_TRANSPORT, + Reqid: r, } if add { rSA.Aead = buildAeadAlgo(k, spi.reverse) @@ -296,6 +299,7 @@ func programSA(localIP, remoteIP net.IP, spi *spi, k *key, dir int, add bool) (f Proto: netlink.XFRM_PROTO_ESP, Spi: spi.forward, Mode: netlink.XFRM_MODE_TRANSPORT, + Reqid: r, } if add { fSA.Aead = buildAeadAlgo(k, spi.forward) @@ -325,17 +329,18 @@ func programSP(fSA *netlink.XfrmState, rSA *netlink.XfrmState, add bool) error { xfrmProgram = ns.NlHandle().XfrmPolicyAdd } - fullMask := net.CIDRMask(8*len(fSA.Src), 8*len(fSA.Src)) + // Create a congruent cidr + s := types.GetMinimalIP(fSA.Src) + d := types.GetMinimalIP(fSA.Dst) + fullMask := net.CIDRMask(8*len(s), 8*len(s)) fPol := &netlink.XfrmPolicy{ - Src: &net.IPNet{IP: fSA.Src, Mask: fullMask}, - Dst: &net.IPNet{IP: fSA.Dst, Mask: fullMask}, + Src: &net.IPNet{IP: s, Mask: fullMask}, + Dst: &net.IPNet{IP: d, Mask: fullMask}, Dir: netlink.XFRM_DIR_OUT, Proto: 17, DstPort: 4789, - Mark: &netlink.XfrmMark{ - Value: mark, - }, + Mark: &spMark, Tmpls: []netlink.XfrmPolicyTmpl{ { Src: fSA.Src, @@ -343,6 +348,7 @@ func programSP(fSA *netlink.XfrmState, rSA *netlink.XfrmState, add bool) error { Proto: netlink.XFRM_PROTO_ESP, Mode: netlink.XFRM_MODE_TRANSPORT, Spi: fSA.Spi, + Reqid: r, }, }, } @@ -426,6 +432,8 @@ func (d *driver) secMapWalk(f func(string, []*spi) ([]*spi, bool)) error { } func (d *driver) setKeys(keys []*key) error { + // Remove any stale policy, state + clearEncryptionStates() // Accept the encryption keys and clear any stale encryption map d.Lock() d.keys = keys @@ -526,7 +534,7 @@ func updateNodeKey(lIP, aIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, pr } if newIdx > -1 { - // +RSA2 + // +rSA2 programSA(lIP, rIP, spis[newIdx], curKeys[newIdx], reverse, true) } @@ -535,16 +543,17 @@ func updateNodeKey(lIP, aIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, pr fSA2, _, _ := programSA(lIP, rIP, spis[priIdx], curKeys[priIdx], forward, true) // +fSP2, -fSP1 - fullMask := net.CIDRMask(8*len(fSA2.Src), 8*len(fSA2.Src)) + s := types.GetMinimalIP(fSA2.Src) + d := types.GetMinimalIP(fSA2.Dst) + fullMask := net.CIDRMask(8*len(s), 8*len(s)) + fSP1 := &netlink.XfrmPolicy{ - Src: &net.IPNet{IP: fSA2.Src, Mask: fullMask}, - Dst: &net.IPNet{IP: fSA2.Dst, Mask: fullMask}, + Src: &net.IPNet{IP: s, Mask: fullMask}, + Dst: &net.IPNet{IP: d, Mask: fullMask}, Dir: netlink.XFRM_DIR_OUT, Proto: 17, DstPort: 4789, - Mark: &netlink.XfrmMark{ - Value: mark, - }, + Mark: &spMark, Tmpls: []netlink.XfrmPolicyTmpl{ { Src: fSA2.Src, @@ -552,6 +561,7 @@ func updateNodeKey(lIP, aIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, pr Proto: netlink.XFRM_PROTO_ESP, Mode: netlink.XFRM_MODE_TRANSPORT, Spi: fSA2.Spi, + Reqid: r, }, }, } @@ -597,3 +607,33 @@ func (n *network) maxMTU() int { } return mtu } + +func clearEncryptionStates() { + nlh := ns.NlHandle() + spList, err := nlh.XfrmPolicyList(netlink.FAMILY_ALL) + if err != nil { + logrus.Warnf("Failed to retrieve SP list for cleanup: %v", err) + } + saList, err := nlh.XfrmStateList(netlink.FAMILY_ALL) + if err != nil { + logrus.Warnf("Failed to retrieve SA list for cleanup: %v", err) + } + for _, sp := range spList { + if sp.Mark != nil && sp.Mark.Value == spMark.Value { + if err := nlh.XfrmPolicyDel(&sp); err != nil { + logrus.Warnf("Failed to delete stale SP %s: %v", sp, err) + continue + } + logrus.Debugf("Removed stale SP: %s", sp) + } + } + for _, sa := range saList { + if sa.Reqid == r { + if err := nlh.XfrmStateDel(&sa); err != nil { + logrus.Warnf("Failed to delete stale SA %s: %v", sa, err) + continue + } + logrus.Debugf("Removed stale SA: %s", sa) + } + } +} diff --git a/vendor/github.com/docker/libnetwork/drivers/overlay/joinleave.go b/vendor/github.com/docker/libnetwork/drivers/overlay/joinleave.go index 26743a12fa..0af09b71ba 100644 --- a/vendor/github.com/docker/libnetwork/drivers/overlay/joinleave.go +++ b/vendor/github.com/docker/libnetwork/drivers/overlay/joinleave.go @@ -145,6 +145,23 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, return nil } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + if tablename != ovPeerTable { + logrus.Errorf("DecodeTableEntry: unexpected table name %s", tablename) + return "", nil + } + + var peer PeerRecord + if err := proto.Unmarshal(value, &peer); err != nil { + logrus.Errorf("DecodeTableEntry: failed to unmarshal peer record for key %s: %v", key, err) + return "", nil + } + + return key, map[string]string{ + "Host IP": peer.TunnelEndpointIP, + } +} + func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { if tableName != ovPeerTable { logrus.Errorf("Unexpected table notification for table %s received", tableName) diff --git a/vendor/github.com/docker/libnetwork/drivers/overlay/ov_network.go b/vendor/github.com/docker/libnetwork/drivers/overlay/ov_network.go index 173cd606d1..d2c6f6784f 100644 --- a/vendor/github.com/docker/libnetwork/drivers/overlay/ov_network.go +++ b/vendor/github.com/docker/libnetwork/drivers/overlay/ov_network.go @@ -159,7 +159,7 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d } if nInfo != nil { - if err := nInfo.TableEventRegister(ovPeerTable); err != nil { + if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil { return err } } diff --git a/vendor/github.com/docker/libnetwork/drivers/overlay/ovmanager/ovmanager.go b/vendor/github.com/docker/libnetwork/drivers/overlay/ovmanager/ovmanager.go index 2c4c771e58..dce8f98b8e 100644 --- a/vendor/github.com/docker/libnetwork/drivers/overlay/ovmanager/ovmanager.go +++ b/vendor/github.com/docker/libnetwork/drivers/overlay/ovmanager/ovmanager.go @@ -199,6 +199,10 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + func (d *driver) DeleteNetwork(nid string) error { return types.NotImplementedErrorf("not implemented") } diff --git a/vendor/github.com/docker/libnetwork/drivers/remote/driver.go b/vendor/github.com/docker/libnetwork/drivers/remote/driver.go index 12dbc121ce..49a7fb4951 100644 --- a/vendor/github.com/docker/libnetwork/drivers/remote/driver.go +++ b/vendor/github.com/docker/libnetwork/drivers/remote/driver.go @@ -116,6 +116,10 @@ func (d *driver) NetworkFree(id string) error { func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + func (d *driver) CreateNetwork(id string, options map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error { create := &api.CreateNetworkRequest{ NetworkID: id, diff --git a/vendor/github.com/docker/libnetwork/drivers/solaris/bridge/bridge.go b/vendor/github.com/docker/libnetwork/drivers/solaris/bridge/bridge.go index 53092efeeb..13dd5f14bc 100644 --- a/vendor/github.com/docker/libnetwork/drivers/solaris/bridge/bridge.go +++ b/vendor/github.com/docker/libnetwork/drivers/solaris/bridge/bridge.go @@ -175,6 +175,10 @@ func (d *driver) NetworkFree(id string) error { func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error { if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" { return types.BadRequestErrorf("ipv4 pool is empty") diff --git a/vendor/github.com/docker/libnetwork/drivers/solaris/overlay/joinleave.go b/vendor/github.com/docker/libnetwork/drivers/solaris/overlay/joinleave.go index f213b1fe7c..fd411988d1 100644 --- a/vendor/github.com/docker/libnetwork/drivers/solaris/overlay/joinleave.go +++ b/vendor/github.com/docker/libnetwork/drivers/solaris/overlay/joinleave.go @@ -149,6 +149,10 @@ func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key stri d.peerAdd(nid, eid, addr.IP, addr.Mask, mac, vtep, true) } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + // Leave method is invoked when a Sandbox detaches from an endpoint. func (d *driver) Leave(nid, eid string) error { if err := validateID(nid, eid); err != nil { diff --git a/vendor/github.com/docker/libnetwork/drivers/solaris/overlay/ov_network.go b/vendor/github.com/docker/libnetwork/drivers/solaris/overlay/ov_network.go index e9b27ba5bd..5e3dd5abe1 100644 --- a/vendor/github.com/docker/libnetwork/drivers/solaris/overlay/ov_network.go +++ b/vendor/github.com/docker/libnetwork/drivers/solaris/overlay/ov_network.go @@ -153,7 +153,7 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d } if nInfo != nil { - if err := nInfo.TableEventRegister(ovPeerTable); err != nil { + if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil { return err } } diff --git a/vendor/github.com/docker/libnetwork/drivers/windows/overlay/joinleave_windows.go b/vendor/github.com/docker/libnetwork/drivers/windows/overlay/joinleave_windows.go index 310b8381af..91dcf28b32 100644 --- a/vendor/github.com/docker/libnetwork/drivers/windows/overlay/joinleave_windows.go +++ b/vendor/github.com/docker/libnetwork/drivers/windows/overlay/joinleave_windows.go @@ -93,6 +93,10 @@ func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key stri d.peerAdd(nid, eid, addr.IP, addr.Mask, mac, vtep, true) } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + // Leave method is invoked when a Sandbox detaches from an endpoint. func (d *driver) Leave(nid, eid string) error { if err := validateID(nid, eid); err != nil { diff --git a/vendor/github.com/docker/libnetwork/drivers/windows/overlay/ov_network_windows.go b/vendor/github.com/docker/libnetwork/drivers/windows/overlay/ov_network_windows.go index 64a9e8af9b..65b7e38d3b 100644 --- a/vendor/github.com/docker/libnetwork/drivers/windows/overlay/ov_network_windows.go +++ b/vendor/github.com/docker/libnetwork/drivers/windows/overlay/ov_network_windows.go @@ -169,7 +169,7 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d n.interfaceName = interfaceName if nInfo != nil { - if err := nInfo.TableEventRegister(ovPeerTable); err != nil { + if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil { return err } } diff --git a/vendor/github.com/docker/libnetwork/drivers/windows/windows.go b/vendor/github.com/docker/libnetwork/drivers/windows/windows.go index 39d862aeb4..b6591a1d6d 100644 --- a/vendor/github.com/docker/libnetwork/drivers/windows/windows.go +++ b/vendor/github.com/docker/libnetwork/drivers/windows/windows.go @@ -183,6 +183,10 @@ func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []d func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) { } +func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) { + return "", nil +} + // Create a new network func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error { if _, err := d.getNetwork(id); err == nil { diff --git a/vendor/github.com/docker/libnetwork/iptables/iptables.go b/vendor/github.com/docker/libnetwork/iptables/iptables.go index d4f4aa23dd..34f7dee09d 100644 --- a/vendor/github.com/docker/libnetwork/iptables/iptables.go +++ b/vendor/github.com/docker/libnetwork/iptables/iptables.go @@ -50,8 +50,7 @@ var ( bestEffortLock sync.Mutex // ErrIptablesNotFound is returned when the rule is not found. ErrIptablesNotFound = errors.New("Iptables not found") - probeOnce sync.Once - firewalldOnce sync.Once + initOnce sync.Once ) // ChainInfo defines the iptables chain. @@ -86,22 +85,32 @@ func initFirewalld() { } } +func detectIptables() { + path, err := exec.LookPath("iptables") + if err != nil { + return + } + iptablesPath = path + supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil + mj, mn, mc, err := GetVersion() + if err != nil { + logrus.Warnf("Failed to read iptables version: %v", err) + return + } + supportsCOpt = supportsCOption(mj, mn, mc) +} + +func initIptables() { + probe() + initFirewalld() + detectIptables() +} + func initCheck() error { + initOnce.Do(initIptables) + if iptablesPath == "" { - probeOnce.Do(probe) - firewalldOnce.Do(initFirewalld) - path, err := exec.LookPath("iptables") - if err != nil { - return ErrIptablesNotFound - } - iptablesPath = path - supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil - mj, mn, mc, err := GetVersion() - if err != nil { - logrus.Warnf("Failed to read iptables version: %v", err) - return nil - } - supportsCOpt = supportsCOption(mj, mn, mc) + return ErrIptablesNotFound } return nil } @@ -189,6 +198,26 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) err } } + establish := []string{ + "-o", bridgeName, + "-m", "conntrack", + "--ctstate", "RELATED,ESTABLISHED", + "-j", "ACCEPT"} + if !Exists(Filter, "FORWARD", establish...) && enable { + insert := append([]string{string(Insert), "FORWARD"}, establish...) + if output, err := Raw(insert...); err != nil { + return err + } else if len(output) != 0 { + return fmt.Errorf("Could not create establish rule to %s: %s", c.Table, output) + } + } else if Exists(Filter, "FORWARD", establish...) && !enable { + del := append([]string{string(Delete), "FORWARD"}, establish...) + if output, err := Raw(del...); err != nil { + return err + } else if len(output) != 0 { + return fmt.Errorf("Could not delete establish rule from %s: %s", c.Table, output) + } + } } return nil } @@ -353,7 +382,11 @@ func exists(native bool, table Table, chain string, rule ...string) bool { table = Filter } - initCheck() + if err := initCheck(); err != nil { + // The exists() signature does not allow us to return an error, but at least + // we can skip the (likely invalid) exec invocation. + return false + } if supportsCOpt { // if exit status is 0 then return true, the rule exists @@ -436,9 +469,9 @@ func ExistChain(chain string, table Table) bool { return false } -// GetVersion reads the iptables version numbers +// GetVersion reads the iptables version numbers during initialization func GetVersion() (major, minor, micro int, err error) { - out, err := Raw("--version") + out, err := exec.Command(iptablesPath, "--version").CombinedOutput() if err == nil { major, minor, micro = parseVersionNumbers(string(out)) } diff --git a/vendor/github.com/docker/libnetwork/network.go b/vendor/github.com/docker/libnetwork/network.go index e5c2eab173..2b9f422538 100644 --- a/vendor/github.com/docker/libnetwork/network.go +++ b/vendor/github.com/docker/libnetwork/network.go @@ -74,6 +74,9 @@ type NetworkInfo interface { // 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. @@ -108,6 +111,11 @@ type servicePorts struct { target []serviceTarget } +type networkDBTable struct { + name string + objType driverapi.ObjectType +} + // IpamConf contains all the ipam related configurations for a network type IpamConf struct { // The master address pool for containers and network interfaces @@ -208,7 +216,7 @@ type network struct { attachable bool inDelete bool ingress bool - driverTables []string + driverTables []networkDBTable dynamic bool sync.Mutex } @@ -1607,11 +1615,18 @@ func (n *network) Labels() map[string]string { return lbls } -func (n *network) TableEventRegister(tableName string) error { +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.Lock() defer n.Unlock() - - n.driverTables = append(n.driverTables, tableName) + n.driverTables = append(n.driverTables, t) return nil } diff --git a/vendor/github.com/docker/libnetwork/networkdb/networkdb.go b/vendor/github.com/docker/libnetwork/networkdb/networkdb.go index c3aab99335..9e5e61caef 100644 --- a/vendor/github.com/docker/libnetwork/networkdb/networkdb.go +++ b/vendor/github.com/docker/libnetwork/networkdb/networkdb.go @@ -307,6 +307,22 @@ func (nDB *NetworkDB) UpdateEntry(tname, nid, key string, value []byte) error { return nil } +// GetTableByNetwork walks the networkdb by the give table and network id and +// returns a map of keys and values +func (nDB *NetworkDB) GetTableByNetwork(tname, nid string) map[string]interface{} { + entries := make(map[string]interface{}) + nDB.indexes[byTable].WalkPrefix(fmt.Sprintf("/%s/%s", tname, nid), func(k string, v interface{}) bool { + entry := v.(*entry) + if entry.deleting { + return false + } + key := k[strings.LastIndex(k, "/")+1:] + entries[key] = entry.value + return false + }) + return entries +} + // DeleteEntry deletes a table entry in NetworkDB for given (network, // table, key) tuple and if the NetworkDB is part of the cluster // propagates this event to the cluster. diff --git a/vendor/github.com/docker/libnetwork/service_common.go b/vendor/github.com/docker/libnetwork/service_common.go index 04f807aea8..049d308423 100644 --- a/vendor/github.com/docker/libnetwork/service_common.go +++ b/vendor/github.com/docker/libnetwork/service_common.go @@ -18,6 +18,26 @@ func newService(name string, id string, ingressPorts []*PortConfig, aliases []st } } +func (c *controller) getLBIndex(sid, nid string, ingressPorts []*PortConfig) int { + skey := serviceKey{ + id: sid, + ports: portConfigs(ingressPorts).String(), + } + c.Lock() + s, ok := c.serviceBindings[skey] + c.Unlock() + + if !ok { + return 0 + } + + s.Lock() + lb := s.loadBalancers[nid] + s.Unlock() + + return int(lb.fwMark) +} + func (c *controller) cleanupServiceBindings(cleanupNID string) { var cleanupFuncs []func() From 14f76a21db71c5a817cb4d1e27f9940cefd5c820 Mon Sep 17 00:00:00 2001 From: Santhosh Manohar Date: Thu, 9 Mar 2017 11:42:10 -0800 Subject: [PATCH 2/2] Enhance network inspect to show all tasks, local & non-local, in swarm mode Signed-off-by: Santhosh Manohar --- api/server/router/network/network_routes.go | 44 ++++++++- api/swagger.yaml | 5 + api/types/network/network.go | 16 +++ api/types/types.go | 27 +++--- cli/command/network/inspect.go | 8 +- cli/command/stack/deploy_composefile.go | 2 +- cli/command/system/inspect.go | 2 +- client/interface.go | 4 +- client/network_inspect.go | 19 +++- client/network_inspect_test.go | 39 ++++++-- docs/api/version-history.md | 1 + docs/reference/commandline/network_inspect.md | 97 ++++++++++++++++++- man/src/network/inspect.md | 93 ++++++++++++++++++ 13 files changed, 320 insertions(+), 37 deletions(-) diff --git a/api/server/router/network/network_routes.go b/api/server/router/network/network_routes.go index 723a7d08a5..63a24c8e10 100644 --- a/api/server/router/network/network_routes.go +++ b/api/server/router/network/network_routes.go @@ -4,10 +4,12 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" "golang.org/x/net/context" + "github.com/docker/docker/api/errors" "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" @@ -65,7 +67,7 @@ SKIP: // run across all the networks. Starting API version 1.27, this detailed // info is available for network specific GET API (equivalent to inspect) if versions.LessThan(httputils.VersionFromContext(ctx), "1.27") { - nr = n.buildDetailedNetworkResources(nw) + nr = n.buildDetailedNetworkResources(nw, false) } else { nr = n.buildNetworkResource(nw) } @@ -85,6 +87,16 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r } term := vars["id"] + var ( + verbose bool + err error + ) + if v := r.URL.Query().Get("verbose"); v != "" { + if verbose, err = strconv.ParseBool(v); err != nil { + err = fmt.Errorf("invalid value for verbose: %s", v) + return errors.NewBadRequestError(err) + } + } // In case multiple networks have duplicate names, return error. // TODO (yongtang): should we wrap with version here for backward compatibility? @@ -100,17 +112,17 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r nw := n.backend.GetNetworks() for _, network := range nw { if network.ID() == term { - return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network)) + return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network, verbose)) } if network.Name() == term { // No need to check the ID collision here as we are still in // local scope and the network ID is unique in this scope. - listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network) + listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network, verbose) } if strings.HasPrefix(network.ID(), term) { // No need to check the ID collision here as we are still in // local scope and the network ID is unique in this scope. - listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network) + listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network, verbose) } } @@ -294,7 +306,7 @@ func (n *networkRouter) buildNetworkResource(nw libnetwork.Network) *types.Netwo return r } -func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network) *types.NetworkResource { +func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network, verbose bool) *types.NetworkResource { if nw == nil { return &types.NetworkResource{} } @@ -315,6 +327,28 @@ func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network) *ty r.Containers[key] = buildEndpointResource(tmpID, e.Name(), ei) } + if !verbose { + return r + } + services := nw.Info().Services() + r.Services = make(map[string]network.ServiceInfo) + for name, service := range services { + tasks := []network.Task{} + for _, t := range service.Tasks { + tasks = append(tasks, network.Task{ + Name: t.Name, + EndpointID: t.EndpointID, + EndpointIP: t.EndpointIP, + Info: t.Info, + }) + } + r.Services[name] = network.ServiceInfo{ + VIP: service.VIP, + Ports: service.Ports, + Tasks: tasks, + LocalLBIndex: service.LocalLBIndex, + } + } return r } diff --git a/api/swagger.yaml b/api/swagger.yaml index 08c8b1e429..eec9d2e922 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -6265,6 +6265,11 @@ paths: description: "Network ID or name" required: true type: "string" + - name: "verbose" + in: "query" + description: "Detailed inspect output for troubleshooting" + type: "boolean" + default: false tags: ["Network"] delete: diff --git a/api/types/network/network.go b/api/types/network/network.go index d04deae6de..8d15ed21b5 100644 --- a/api/types/network/network.go +++ b/api/types/network/network.go @@ -60,6 +60,22 @@ type EndpointSettings struct { MacAddress string } +// Task carries the information about one backend task +type Task struct { + Name string + EndpointID string + EndpointIP string + Info map[string]string +} + +// ServiceInfo represents service parameters with the list of service's tasks +type ServiceInfo struct { + VIP string + Ports []string + LocalLBIndex int + Tasks []Task +} + // Copy makes a deep copy of `EndpointSettings` func (es *EndpointSettings) Copy() *EndpointSettings { epCopy := *es diff --git a/api/types/types.go b/api/types/types.go index 509a78b633..25f3664e99 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -390,19 +390,20 @@ type MountPoint struct { // NetworkResource is the body of the "get network" http response message type NetworkResource struct { - Name string // Name is the requested name of the network - ID string `json:"Id"` // ID uniquely identifies a network on a single machine - Created time.Time // Created is the time the network created - Scope string // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level) - Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`) - EnableIPv6 bool // EnableIPv6 represents whether to enable IPv6 - IPAM network.IPAM // IPAM is the network's IP Address Management - Internal bool // Internal represents if the network is used internal only - Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode. - Containers map[string]EndpointResource // Containers contains endpoints belonging to the network - Options map[string]string // Options holds the network specific options to use for when creating the network - Labels map[string]string // Labels holds metadata specific to the network being created - Peers []network.PeerInfo `json:",omitempty"` // List of peer nodes for an overlay network + Name string // Name is the requested name of the network + ID string `json:"Id"` // ID uniquely identifies a network on a single machine + Created time.Time // Created is the time the network created + Scope string // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level) + Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`) + EnableIPv6 bool // EnableIPv6 represents whether to enable IPv6 + IPAM network.IPAM // IPAM is the network's IP Address Management + Internal bool // Internal represents if the network is used internal only + Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode. + Containers map[string]EndpointResource // Containers contains endpoints belonging to the network + Options map[string]string // Options holds the network specific options to use for when creating the network + Labels map[string]string // Labels holds metadata specific to the network being created + Peers []network.PeerInfo `json:",omitempty"` // List of peer nodes for an overlay network + Services map[string]network.ServiceInfo `json:",omitempty"` } // EndpointResource contains network resources allocated and used for a container in a network diff --git a/cli/command/network/inspect.go b/cli/command/network/inspect.go index 1a86855f71..e58d66b77a 100644 --- a/cli/command/network/inspect.go +++ b/cli/command/network/inspect.go @@ -10,8 +10,9 @@ import ( ) type inspectOptions struct { - format string - names []string + format string + names []string + verbose bool } func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { @@ -28,6 +29,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { } cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template") + cmd.Flags().BoolVarP(&opts.verbose, "verbose", "v", false, "Verbose output for diagnostics") return cmd } @@ -38,7 +40,7 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { ctx := context.Background() getNetFunc := func(name string) (interface{}, []byte, error) { - return client.NetworkInspectWithRaw(ctx, name) + return client.NetworkInspectWithRaw(ctx, name, opts.verbose) } return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc) diff --git a/cli/command/stack/deploy_composefile.go b/cli/command/stack/deploy_composefile.go index 72f9b8aac9..3e62494325 100644 --- a/cli/command/stack/deploy_composefile.go +++ b/cli/command/stack/deploy_composefile.go @@ -140,7 +140,7 @@ func validateExternalNetworks( client := dockerCli.Client() for _, networkName := range externalNetworks { - network, err := client.NetworkInspect(ctx, networkName) + network, err := client.NetworkInspect(ctx, networkName, false) if err != nil { if dockerclient.IsErrNetworkNotFound(err) { return fmt.Errorf("network %q is declared as external, but could not be found. You need to create the network before the stack is deployed (with overlay driver)", networkName) diff --git a/cli/command/system/inspect.go b/cli/command/system/inspect.go index c86e858a29..6bb9cbe041 100644 --- a/cli/command/system/inspect.go +++ b/cli/command/system/inspect.go @@ -67,7 +67,7 @@ func inspectImages(ctx context.Context, dockerCli *command.DockerCli) inspect.Ge func inspectNetwork(ctx context.Context, dockerCli *command.DockerCli) inspect.GetRefFunc { return func(ref string) (interface{}, []byte, error) { - return dockerCli.Client().NetworkInspectWithRaw(ctx, ref) + return dockerCli.Client().NetworkInspectWithRaw(ctx, ref, false) } } diff --git a/client/interface.go b/client/interface.go index 5823eed883..ae4146bb4a 100644 --- a/client/interface.go +++ b/client/interface.go @@ -91,8 +91,8 @@ type NetworkAPIClient interface { NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error - NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) - NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) + NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) + NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) NetworkRemove(ctx context.Context, networkID string) error NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) diff --git a/client/network_inspect.go b/client/network_inspect.go index 5ad4ea5bf3..7242304025 100644 --- a/client/network_inspect.go +++ b/client/network_inspect.go @@ -5,21 +5,30 @@ import ( "encoding/json" "io/ioutil" "net/http" + "net/url" "github.com/docker/docker/api/types" "golang.org/x/net/context" ) // NetworkInspect returns the information for a specific network configured in the docker host. -func (cli *Client) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) { - networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID) +func (cli *Client) NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) { + networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, verbose) return networkResource, err } // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation. -func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) { - var networkResource types.NetworkResource - resp, err := cli.get(ctx, "/networks/"+networkID, nil, nil) +func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) { + var ( + networkResource types.NetworkResource + resp serverResponse + err error + ) + query := url.Values{} + if verbose { + query.Set("verbose", "true") + } + resp, err = cli.get(ctx, "/networks/"+networkID, query, nil) if err != nil { if resp.statusCode == http.StatusNotFound { return networkResource, nil, networkNotFoundError{networkID} diff --git a/client/network_inspect_test.go b/client/network_inspect_test.go index 55f04eca2c..1504289f5d 100644 --- a/client/network_inspect_test.go +++ b/client/network_inspect_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" "golang.org/x/net/context" ) @@ -18,7 +19,7 @@ func TestNetworkInspectError(t *testing.T) { client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")), } - _, err := client.NetworkInspect(context.Background(), "nothing") + _, err := client.NetworkInspect(context.Background(), "nothing", false) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } @@ -29,7 +30,7 @@ func TestNetworkInspectContainerNotFound(t *testing.T) { client: newMockClient(errorMock(http.StatusNotFound, "Server error")), } - _, err := client.NetworkInspect(context.Background(), "unknown") + _, err := client.NetworkInspect(context.Background(), "unknown", false) if err == nil || !IsErrNetworkNotFound(err) { t.Fatalf("expected a networkNotFound error, got %v", err) } @@ -46,9 +47,23 @@ func TestNetworkInspect(t *testing.T) { return nil, fmt.Errorf("expected GET method, got %s", req.Method) } - content, err := json.Marshal(types.NetworkResource{ - Name: "mynetwork", - }) + var ( + content []byte + err error + ) + if strings.HasPrefix(req.URL.RawQuery, "verbose=true") { + s := map[string]network.ServiceInfo{ + "web": {}, + } + content, err = json.Marshal(types.NetworkResource{ + Name: "mynetwork", + Services: s, + }) + } else { + content, err = json.Marshal(types.NetworkResource{ + Name: "mynetwork", + }) + } if err != nil { return nil, err } @@ -59,11 +74,23 @@ func TestNetworkInspect(t *testing.T) { }), } - r, err := client.NetworkInspect(context.Background(), "network_id") + r, err := client.NetworkInspect(context.Background(), "network_id", false) if err != nil { t.Fatal(err) } if r.Name != "mynetwork" { t.Fatalf("expected `mynetwork`, got %s", r.Name) } + + r, err = client.NetworkInspect(context.Background(), "network_id", true) + if err != nil { + t.Fatal(err) + } + if r.Name != "mynetwork" { + t.Fatalf("expected `mynetwork`, got %s", r.Name) + } + _, ok := r.Services["web"] + if !ok { + t.Fatalf("expected service `web` missing in the verbose output") + } } diff --git a/docs/api/version-history.md b/docs/api/version-history.md index 541111c14a..4fdd357c4b 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -17,6 +17,7 @@ keywords: "API, Docker, rcli, REST, documentation" [Docker Engine API v1.27](https://docs.docker.com/engine/api/v1.27/) documentation +* Optional query parameter `verbose` for `GET /networks/(id or name)` will now list all services with all the tasks, including the non-local tasks on the given network. * `GET /containers/(id or name)/attach/ws` now returns WebSocket in binary frame format for API version >= v1.27, and returns WebSocket in text frame format for API version< v1.27, for the purpose of backward-compatibility. * `GET /networks` is optimised only to return list of all networks and network specific information. List of all containers attached to a specific network is removed from this API and is only available using the network specific `GET /networks/{network-id}. * `GET /containers/json` now supports `publish` and `expose` filters to filter containers that expose or publish certain ports. diff --git a/docs/reference/commandline/network_inspect.md b/docs/reference/commandline/network_inspect.md index 2b4c423bbb..1a856ddcb1 100644 --- a/docs/reference/commandline/network_inspect.md +++ b/docs/reference/commandline/network_inspect.md @@ -48,7 +48,7 @@ The `network inspect` command shows the containers, by id, in its results. For networks backed by multi-host network driver, such as Overlay, this command also shows the container endpoints in other hosts in the cluster. These endpoints are represented as "ep-{endpoint-id}" in the output. -However, for swarm-scoped networks, only the endpoints that are local to the +However, for swarm mode networks, only the endpoints that are local to the node are shown. You can specify an alternate format to execute a given @@ -201,6 +201,101 @@ $ docker network inspect ingress ] ``` +### Using `verbose` option for `network inspect` + +`docker network inspect --verbose` for swarm mode overlay networks shows service-specific +details such as the service's VIP and port mappings. It also shows IPs of service tasks, +and the IPs of the nodes where the tasks are running. + +Following is an example output for a overlay network `ov1` that has one service `s1` +attached to. service `s1` in this case has three replicas. + +```bash +$ docker network inspect --verbose ov1 +[ + { + "Name": "ov1", + "Id": "ybmyjvao9vtzy3oorxbssj13b", + "Created": "2017-03-13T17:04:39.776106792Z", + "Scope": "swarm", + "Driver": "overlay", + "EnableIPv6": false, + "IPAM": { + "Driver": "default", + "Options": null, + "Config": [ + { + "Subnet": "10.0.0.0/24", + "Gateway": "10.0.0.1" + } + ] + }, + "Internal": false, + "Attachable": false, + "Containers": { + "020403bd88a15f60747fd25d1ad5fa1272eb740e8a97fc547d8ad07b2f721c5e": { + "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o", + "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e", + "MacAddress": "02:42:0a:00:00:04", + "IPv4Address": "10.0.0.4/24", + "IPv6Address": "" + } + }, + "Options": { + "com.docker.network.driver.overlay.vxlanid_list": "4097" + }, + "Labels": {}, + "Peers": [ + { + "Name": "net-3-5d3cfd30a58c", + "IP": "192.168.33.13" + }, + { + "Name": "net-1-6ecbc0040a73", + "IP": "192.168.33.11" + }, + { + "Name": "net-2-fb80208efd75", + "IP": "192.168.33.12" + } + ], + "Services": { + "s1": { + "VIP": "10.0.0.2", + "Ports": [], + "LocalLBIndex": 257, + "Tasks": [ + { + "Name": "s1.2.q4hcq2aiiml25ubtrtg4q1txt", + "EndpointID": "040879b027e55fb658e8b60ae3b87c6cdac7d291e86a190a3b5ac6567b26511a", + "EndpointIP": "10.0.0.5", + "Info": { + "Host IP": "192.168.33.11" + } + }, + { + "Name": "s1.3.yawl4cgkp7imkfx469kn9j6lm", + "EndpointID": "106edff9f120efe44068b834e1cddb5b39dd4a3af70211378b2f7a9e562bbad8", + "EndpointIP": "10.0.0.3", + "Info": { + "Host IP": "192.168.33.12" + } + }, + { + "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o", + "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e", + "EndpointIP": "10.0.0.4", + "Info": { + "Host IP": "192.168.33.13" + } + } + ] + } + } + } +] +``` + ## Related commands * [network disconnect ](network_disconnect.md) diff --git a/man/src/network/inspect.md b/man/src/network/inspect.md index 89fd0e167a..a61dfd8c10 100644 --- a/man/src/network/inspect.md +++ b/man/src/network/inspect.md @@ -86,3 +86,96 @@ $ docker network inspect simple-network } ] ``` + +`docker network inspect --verbose` for swarm mode overlay networks shows service-specific +details such as the service's VIP and port mappings. It also shows IPs of service tasks, +and the IPs of the nodes where the tasks are running. + +Following is an example output for a overlay network `ov1` that has one service `s1` +attached to. service `s1` in this case has three replicas. + +```bash +$ docker network inspect --verbose ov1 +[ + { + "Name": "ov1", + "Id": "ybmyjvao9vtzy3oorxbssj13b", + "Created": "2017-03-13T17:04:39.776106792Z", + "Scope": "swarm", + "Driver": "overlay", + "EnableIPv6": false, + "IPAM": { + "Driver": "default", + "Options": null, + "Config": [ + { + "Subnet": "10.0.0.0/24", + "Gateway": "10.0.0.1" + } + ] + }, + "Internal": false, + "Attachable": false, + "Containers": { + "020403bd88a15f60747fd25d1ad5fa1272eb740e8a97fc547d8ad07b2f721c5e": { + "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o", + "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e", + "MacAddress": "02:42:0a:00:00:04", + "IPv4Address": "10.0.0.4/24", + "IPv6Address": "" + } + }, + "Options": { + "com.docker.network.driver.overlay.vxlanid_list": "4097" + }, + "Labels": {}, + "Peers": [ + { + "Name": "net-3-5d3cfd30a58c", + "IP": "192.168.33.13" + }, + { + "Name": "net-1-6ecbc0040a73", + "IP": "192.168.33.11" + }, + { + "Name": "net-2-fb80208efd75", + "IP": "192.168.33.12" + } + ], + "Services": { + "s1": { + "VIP": "10.0.0.2", + "Ports": [], + "LocalLBIndex": 257, + "Tasks": [ + { + "Name": "s1.2.q4hcq2aiiml25ubtrtg4q1txt", + "EndpointID": "040879b027e55fb658e8b60ae3b87c6cdac7d291e86a190a3b5ac6567b26511a", + "EndpointIP": "10.0.0.5", + "Info": { + "Host IP": "192.168.33.11" + } + }, + { + "Name": "s1.3.yawl4cgkp7imkfx469kn9j6lm", + "EndpointID": "106edff9f120efe44068b834e1cddb5b39dd4a3af70211378b2f7a9e562bbad8", + "EndpointIP": "10.0.0.3", + "Info": { + "Host IP": "192.168.33.12" + } + }, + { + "Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o", + "EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e", + "EndpointIP": "10.0.0.4", + "Info": { + "Host IP": "192.168.33.13" + } + } + ] + } + } + } +] +```