diff --git a/app/api_topologies.go b/app/api_topologies.go index deefe81f1..ece945363 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -147,6 +147,12 @@ func init() { Name: "Hosts", Rank: 4, }, + APITopologyDesc{ + id: "weave", + parent: "hosts", + renderer: render.WeaveRenderer, + Name: "Weave Net", + }, ) } diff --git a/app/api_topology.go b/app/api_topology.go index d9d78b356..08bfe229c 100644 --- a/app/api_topology.go +++ b/app/api_topology.go @@ -84,7 +84,7 @@ func handleWebsocket( for { // just discard everything the browser sends if _, _, err := c.ReadMessage(); err != nil { if !xfer.IsExpectedWSCloseError(err) { - log.Println("err:", err) + log.Error("err:", err) } close(quit) break diff --git a/common/weave/client.go b/common/weave/client.go index 9199e4ec3..8f0ed8d82 100644 --- a/common/weave/client.go +++ b/common/weave/client.go @@ -25,17 +25,25 @@ type Client interface { // Status describes whats happen in the Weave Net router. type Status struct { - Router Router - DNS DNS - IPAM IPAM + Version string + Router Router + DNS DNS + IPAM IPAM } // Router describes the status of the Weave Router type Router struct { Name string Peers []struct { - Name string - NickName string + Name string + NickName string + Connections []struct { + Name string + NickName string + Address string + Outbound bool + Established bool + } } } diff --git a/common/weave/client_test.go b/common/weave/client_test.go index 362de7c0d..02a17e976 100644 --- a/common/weave/client_test.go +++ b/common/weave/client_test.go @@ -65,8 +65,15 @@ func TestStatus(t *testing.T) { want := weave.Status{ Router: weave.Router{ Peers: []struct { - Name string - NickName string + Name string + NickName string + Connections []struct { + Name string + NickName string + Address string + Outbound bool + Established bool + } }{ { Name: mockWeavePeerName, diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index f591c2e84..48971061c 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -13,11 +13,10 @@ import ( // Keys for use in Node const ( - ImageID = "docker_image_id" - ImageName = "docker_image_name" - ImageLabelPrefix = "docker_image_label_" - OverlayPeerPrefix = "docker_peer_" - IsInHostNetwork = "docker_is_in_host_network" + ImageID = "docker_image_id" + ImageName = "docker_image_name" + ImageLabelPrefix = "docker_image_label_" + IsInHostNetwork = "docker_is_in_host_network" ) // Exposed for testing @@ -261,10 +260,9 @@ func (r *Reporter) overlayTopology() report.Topology { } }) - peerID := OverlayPeerPrefix + r.hostID // Add both local and global networks to the LocalNetworks Set // since we treat container IPs as local - node := report.MakeNode(report.MakeOverlayNodeID(peerID)).WithSets( + node := report.MakeNode(report.MakeOverlayNodeID(report.DockerOverlayPeerPrefix, r.hostID)).WithSets( report.MakeSets().Add(host.LocalNetworks, report.MakeStringSet(subnets...))) return report.MakeTopology().AddNode(node) } diff --git a/probe/docker/reporter_test.go b/probe/docker/reporter_test.go index 1a81bc0fc..a6d48bb72 100644 --- a/probe/docker/reporter_test.go +++ b/probe/docker/reporter_test.go @@ -134,8 +134,7 @@ func TestReporter(t *testing.T) { // Reporter should add a container network { - peerID := docker.OverlayPeerPrefix + hostID - overlayNodeID := report.MakeOverlayNodeID(peerID) + overlayNodeID := report.MakeOverlayNodeID(report.DockerOverlayPeerPrefix, hostID) node, ok := rpt.Overlay.Nodes[overlayNodeID] if !ok { t.Fatalf("Expected report to have overlay node %q, but not found", overlayNodeID) diff --git a/probe/host/tagger.go b/probe/host/tagger.go index 8b0b7ec46..2896e9215 100644 --- a/probe/host/tagger.go +++ b/probe/host/tagger.go @@ -29,9 +29,9 @@ func (t Tagger) Tag(r report.Report) (report.Report, error) { parents = report.EmptySets.Add(report.Host, report.MakeStringSet(t.hostNodeID)) ) - // Explicitly don't tag Endpoints and Addresses - These topologies include pseudo nodes, - // and as such do their own host tagging - for _, topology := range []report.Topology{r.Process, r.Container, r.ContainerImage, r.Host, r.Overlay, r.Pod} { + // Explicitly don't tag Endpoints, Addresses and Overlay nodes - These topologies include pseudo nodes, + // and as such do their own host tagging. + for _, topology := range []report.Topology{r.Process, r.Container, r.ContainerImage, r.Host, r.Pod} { for _, node := range topology.Nodes { topology.AddNode(node.WithLatests(metadata).WithParents(parents)) } diff --git a/probe/overlay/weave.go b/probe/overlay/weave.go index 913117cd8..eb0608e32 100644 --- a/probe/overlay/weave.go +++ b/probe/overlay/weave.go @@ -6,6 +6,7 @@ import ( "time" "github.com/weaveworks/scope/common/backoff" + "github.com/weaveworks/scope/common/mtime" "github.com/weaveworks/scope/common/weave" "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/probe/host" @@ -26,6 +27,9 @@ const ( // WeaveMACAddress is the key for the mac address of the container on the // weave network, to be found in container node metadata WeaveMACAddress = "weave_mac_address" + + // WeaveVersion is the key for the weave version running on the peer + WeaveVersion = "weave_version" ) // Weave represents a single Weave router, presumably on the same host @@ -162,16 +166,36 @@ func (w *Weave) Report() (report.Report, error) { WeaveMACAddress: {ID: WeaveMACAddress, Label: "Weave MAC", From: report.FromLatest, Priority: 17}, WeaveDNSHostname: {ID: WeaveDNSHostname, Label: "Weave DNS Name", From: report.FromLatest, Priority: 18}, }) - for _, peer := range w.statusCache.Router.Peers { - r.Overlay.AddNode(report.MakeNodeWith(report.MakeOverlayNodeID(peer.Name), map[string]string{ - WeavePeerName: peer.Name, - WeavePeerNickName: peer.NickName, - })) + r.Overlay = r.Overlay.WithMetadataTemplates(report.MetadataTemplates{ + WeavePeerName: {ID: WeavePeerName, Label: "Peer Name", From: report.FromLatest, Truncate: 17, Priority: 1}, + WeaveVersion: {ID: WeaveVersion, Label: "Version", From: report.FromLatest, Priority: 2}, + }) + // We report nodes for all peers (not just the current node) to highlight peers not monitored by Scope + // (i.e. without a running probe) + // Note: this will cause redundant information (n^2) if all peers have a running probe + for _, peer := range w.statusCache.Router.Peers { + node := report.MakeNodeWith(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, peer.Name), + map[string]string{ + WeavePeerName: peer.Name, + WeavePeerNickName: peer.NickName, + }) + // Peer corresponding to current host + if peer.Name == w.statusCache.Router.Name { + node = node.WithLatest(report.HostNodeID, mtime.Now(), w.hostID) + node = node.WithLatest(WeaveVersion, mtime.Now(), w.statusCache.Version) + node = node.WithParents(report.EmptySets.Add(report.Host, report.MakeStringSet(w.hostID))) + } + for _, conn := range peer.Connections { + if conn.Outbound { + node = node.WithAdjacent(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, conn.Name)) + } + } + r.Overlay.AddNode(node) } if w.statusCache.IPAM.DefaultSubnet != "" { r.Overlay.AddNode( - report.MakeNode(report.MakeOverlayNodeID(w.statusCache.Router.Name)).WithSets( + report.MakeNode(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, w.statusCache.Router.Name)).WithSets( report.MakeSets().Add(host.LocalNetworks, report.MakeStringSet(w.statusCache.IPAM.DefaultSubnet)), ), ) diff --git a/probe/overlay/weave_test.go b/probe/overlay/weave_test.go index fc7161c5b..aec56548b 100644 --- a/probe/overlay/weave_test.go +++ b/probe/overlay/weave_test.go @@ -35,7 +35,7 @@ func TestWeaveTaggerOverlayTopology(t *testing.T) { t.Fatal(err) } - nodeID := report.MakeOverlayNodeID(weave.MockWeavePeerName) + nodeID := report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, weave.MockWeavePeerName) node, ok := have.Overlay.Nodes[nodeID] if !ok { t.Errorf("Expected overlay node %q, but not found", nodeID) diff --git a/render/detailed/summary.go b/render/detailed/summary.go index 6f5f59698..caa2d0a3e 100644 --- a/render/detailed/summary.go +++ b/render/detailed/summary.go @@ -8,6 +8,7 @@ import ( "github.com/weaveworks/scope/probe/endpoint" "github.com/weaveworks/scope/probe/host" "github.com/weaveworks/scope/probe/kubernetes" + "github.com/weaveworks/scope/probe/overlay" "github.com/weaveworks/scope/probe/process" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" @@ -68,6 +69,7 @@ var renderers = map[string]func(NodeSummary, report.Node) (NodeSummary, bool){ report.Deployment: podGroupNodeSummary, report.ReplicaSet: podGroupNodeSummary, report.Host: hostNodeSummary, + report.Overlay: weaveNodeSummary, } var templates = map[string]struct{ Label, LabelMinor string }{ @@ -253,6 +255,18 @@ func hostNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { return base, true } +func weaveNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) { + var ( + nickname, _ = n.Latest.Lookup(overlay.WeavePeerNickName) + ) + + _, peerName := report.ParseOverlayNodeID(n.ID) + + base.Label, base.LabelMinor = nickname, peerName + + return base, true +} + // groupNodeSummary renders the summary for a group node. n.Topology is // expected to be of the form: group:container:hostname func groupNodeSummary(base NodeSummary, r report.Report, n report.Node) (NodeSummary, bool) { @@ -298,6 +312,7 @@ type NodeSummaries map[string]NodeSummary // Summaries converts RenderableNodes into a set of NodeSummaries func Summaries(r report.Report, rns report.Nodes) NodeSummaries { + result := NodeSummaries{} for id, node := range rns { if summary, ok := MakeNodeSummary(r, node); ok { diff --git a/render/selectors.go b/render/selectors.go index fa5344749..286bc29ee 100644 --- a/render/selectors.go +++ b/render/selectors.go @@ -31,4 +31,5 @@ var ( SelectService = TopologySelector(report.Service) SelectDeployment = TopologySelector(report.Deployment) SelectReplicaSet = TopologySelector(report.ReplicaSet) + SelectOverlay = TopologySelector(report.Overlay) ) diff --git a/render/weave.go b/render/weave.go new file mode 100644 index 000000000..216a0cf1c --- /dev/null +++ b/render/weave.go @@ -0,0 +1,35 @@ +package render + +import ( + "github.com/weaveworks/scope/probe/overlay" + "github.com/weaveworks/scope/report" +) + +// WeaveRenderer is a Renderer which produces a renderable weave topology. +var WeaveRenderer = MakeMap( + MapWeaveIdentity, + SelectOverlay, +) + +// MapWeaveIdentity maps an overlay topology node to a weave topology node. +func MapWeaveIdentity(m report.Node, _ report.Networks) report.Nodes { + peerPrefix, _ := report.ParseOverlayNodeID(m.ID) + if peerPrefix != report.WeaveOverlayPeerPrefix { + return nil + } + + var ( + node = m + nickname, _ = m.Latest.Lookup(overlay.WeavePeerNickName) + ) + + // Nodes without a host id indicate they are not monitored by Scope + // (their info doesn't come from a probe monitoring that peer directly) + // , display them as pseudo nodes. + if _, ok := node.Latest.Lookup(report.HostNodeID); !ok { + id := MakePseudoNodeID(UnmanagedID, nickname) + node = NewDerivedPseudoNode(id, m) + } + + return report.Nodes{node.ID: node} +} diff --git a/report/id.go b/report/id.go index dd1c3720f..0f5466e80 100644 --- a/report/id.go +++ b/report/id.go @@ -22,6 +22,12 @@ const ( // Key added to nodes to prevent them being joined with conntracked connections DoesNotMakeConnections = "does_not_make_connections" + + // WeaveOverlayPeerPrefix is the prefix for weave peers in the overlay network + WeaveOverlayPeerPrefix = "" + + // DockerOverlayPeerPrefix is the prefix for docker peers in the overlay network + DockerOverlayPeerPrefix = "docker_peer_" ) // MakeEndpointNodeID produces an endpoint node ID from its composite parts. @@ -134,9 +140,26 @@ func parseSingleComponentID(tag string) func(string) (string, bool) { } // MakeOverlayNodeID produces an overlay topology node ID from a router peer's -// name, which is assumed to be globally unique. -func MakeOverlayNodeID(peerName string) string { - return "#" + peerName +// prefix and name, which is assumed to be globally unique. +func MakeOverlayNodeID(peerPrefix, peerName string) string { + return "#" + peerPrefix + peerName +} + +// ParseOverlayNodeID produces the overlay type and peer name. +func ParseOverlayNodeID(id string) (overlayPrefix string, peerName string) { + + if !strings.HasPrefix(id, "#") { + // Best we can do + return "", "" + } + + id = id[1:] + + if strings.HasPrefix(id, DockerOverlayPeerPrefix) { + return DockerOverlayPeerPrefix, id[len(DockerOverlayPeerPrefix):] + } + + return WeaveOverlayPeerPrefix, id } // ParseNodeID produces the host ID and remainder (typically an address) from diff --git a/test/weave/mock.go b/test/weave/mock.go index 6cd5ed0b9..ff59c9a4b 100644 --- a/test/weave/mock.go +++ b/test/weave/mock.go @@ -26,8 +26,15 @@ func (MockClient) Status() (weave.Status, error) { Router: weave.Router{ Name: MockWeavePeerName, Peers: []struct { - Name string - NickName string + Name string + NickName string + Connections []struct { + Name string + NickName string + Address string + Outbound bool + Established bool + } }{ { Name: MockWeavePeerName,