diff --git a/common/weave/client.go b/common/weave/client.go index 8f0ed8d82..359898ceb 100644 --- a/common/weave/client.go +++ b/common/weave/client.go @@ -27,29 +27,47 @@ type Client interface { type Status struct { Version string Router Router - DNS DNS - IPAM IPAM + DNS *DNS + IPAM *IPAM } // Router describes the status of the Weave Router type Router struct { - Name string - Peers []struct { + Name string + Encryption bool + ProtocolMinVersion int + ProtocolMaxVersion int + PeerDiscovery bool + Peers []Peer + Connections []struct { + Address string + Outbound bool + State string + Info string + } + Targets []string + TrustedSubnets []string +} + +// Peer describes a peer in the weave network +type Peer struct { + Name string + NickName string + Connections []struct { Name string NickName string - Connections []struct { - Name string - NickName string - Address string - Outbound bool - Established bool - } + Address string + Outbound bool + Established bool } } // DNS describes the status of Weave DNS type DNS struct { - Entries []struct { + Domain string + Upstream []string + TTL uint32 + Entries []struct { Hostname string ContainerID string Tombstone int64 @@ -58,7 +76,18 @@ type DNS struct { // IPAM describes the status of Weave IPAM type IPAM struct { + Paxos *struct { + Elector bool + KnownNodes int + Quorum uint + } + Range string DefaultSubnet string + Entries []struct { + Size uint32 + IsKnownPeer bool + } + PendingAllocates []string } var weavePsMatch = regexp.MustCompile(`^([0-9a-f]{12}) ((?:[0-9a-f][0-9a-f]\:){5}(?:[0-9a-f][0-9a-f]))(.*)$`) diff --git a/common/weave/client_test.go b/common/weave/client_test.go index 02a17e976..6dfe9168a 100644 --- a/common/weave/client_test.go +++ b/common/weave/client_test.go @@ -64,24 +64,14 @@ func TestStatus(t *testing.T) { want := weave.Status{ Router: weave.Router{ - Peers: []struct { - Name string - NickName string - Connections []struct { - Name string - NickName string - Address string - Outbound bool - Established bool - } - }{ + Peers: []weave.Peer{ { Name: mockWeavePeerName, NickName: mockWeavePeerNickName, }, }, }, - DNS: weave.DNS{ + DNS: &weave.DNS{ Entries: []struct { Hostname string ContainerID string diff --git a/probe/docker/controls_test.go b/probe/docker/controls_test.go index d87cdffce..cb0490e1d 100644 --- a/probe/docker/controls_test.go +++ b/probe/docker/controls_test.go @@ -17,7 +17,7 @@ func TestControls(t *testing.T) { mdc := newMockClient() setupStubs(mdc, func() { hr := controls.NewDefaultHandlerRegistry() - registry, _ := docker.NewRegistry(10*time.Second, nil, false, "", hr) + registry, _ := docker.NewRegistry(10*time.Second, nil, false, "", hr, "") defer registry.Stop() for _, tc := range []struct{ command, result string }{ @@ -58,7 +58,7 @@ func TestPipes(t *testing.T) { mdc := newMockClient() setupStubs(mdc, func() { hr := controls.NewDefaultHandlerRegistry() - registry, _ := docker.NewRegistry(10*time.Second, nil, false, "", hr) + registry, _ := docker.NewRegistry(10*time.Second, nil, false, "", hr, "") defer registry.Stop() test.Poll(t, 100*time.Millisecond, true, func() interface{} { diff --git a/probe/docker/registry.go b/probe/docker/registry.go index 796ba7cf7..2fd3bcef8 100644 --- a/probe/docker/registry.go +++ b/probe/docker/registry.go @@ -25,7 +25,6 @@ const ( UnpauseEvent = "unpause" NetworkConnectEvent = "network:connect" NetworkDisconnectEvent = "network:disconnect" - endpoint = "unix:///var/run/docker.sock" ) // Vars exported for testing. @@ -95,8 +94,8 @@ func newDockerClient(endpoint string) (Client, error) { } // NewRegistry returns a usable Registry. Don't forget to Stop it. -func NewRegistry(interval time.Duration, pipes controls.PipeClient, collectStats bool, hostID string, handlerRegistry *controls.HandlerRegistry) (Registry, error) { - client, err := NewDockerClientStub(endpoint) +func NewRegistry(interval time.Duration, pipes controls.PipeClient, collectStats bool, hostID string, handlerRegistry *controls.HandlerRegistry, dockerEndpoint string) (Registry, error) { + client, err := NewDockerClientStub(dockerEndpoint) if err != nil { return nil, err } diff --git a/probe/docker/registry_test.go b/probe/docker/registry_test.go index 71428ed08..2083ed15d 100644 --- a/probe/docker/registry_test.go +++ b/probe/docker/registry_test.go @@ -21,7 +21,7 @@ import ( func testRegistry() docker.Registry { hr := controls.NewDefaultHandlerRegistry() - registry, _ := docker.NewRegistry(10*time.Second, nil, true, "", hr) + registry, _ := docker.NewRegistry(10*time.Second, nil, true, "", hr, "") return registry } diff --git a/probe/overlay/weave.go b/probe/overlay/weave.go index eb0608e32..9fc32e2d5 100644 --- a/probe/overlay/weave.go +++ b/probe/overlay/weave.go @@ -1,61 +1,153 @@ package overlay import ( + "bytes" + "fmt" + "regexp" "strings" "sync" "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" "github.com/weaveworks/scope/report" + + docker_client "github.com/fsouza/go-dockerclient" ) +// Keys for use in Node const ( - // WeavePeerName is the key for the peer name, typically a MAC address. - WeavePeerName = "weave_peer_name" - - // WeavePeerNickName is the key for the peer nickname, typically a - // hostname. - WeavePeerNickName = "weave_peer_nick_name" - - // WeaveDNSHostname is the ket for the WeaveDNS hostname - WeaveDNSHostname = "weave_dns_hostname" - - // 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" + WeavePeerName = "weave_peer_name" + WeavePeerNickName = "weave_peer_nick_name" + WeaveDNSHostname = "weave_dns_hostname" + WeaveMACAddress = "weave_mac_address" + WeaveVersion = "weave_version" + WeaveEncryption = "weave_encryption" + WeaveProtocol = "weave_protocol" + WeavePeerDiscovery = "weave_peer_discovery" + WeaveTargetCount = "weave_target_count" + WeaveConnectionCount = "weave_connection_count" + WeavePeerCount = "weave_peer_count" + WeaveTrustedSubnets = "weave_trusted_subnet_count" + WeaveIPAMTableID = "weave_ipam_table" + WeaveIPAMStatus = "weave_ipam_status" + WeaveIPAMRange = "weave_ipam_range" + WeaveIPAMDefaultSubnet = "weave_ipam_default_subnet" + WeaveDNSTableID = "weave_dns_table" + WeaveDNSDomain = "weave_dns_domain" + WeaveDNSUpstream = "weave_dns_upstream" + WeaveDNSTTL = "weave_dns_ttl" + WeaveDNSEntryCount = "weave_dns_entry_count" + WeaveProxyTableID = "weave_proxy_table" + WeaveProxyStatus = "weave_proxy_status" + WeaveProxyAddress = "weave_proxy_address" + WeavePluginTableID = "weave_plugin_table" + WeavePluginStatus = "weave_plugin_status" + WeavePluginDriver = "weave_plugin_driver" ) +var ( + // NewDockerClientStub is used for testing + NewDockerClientStub = newDockerClient + + containerNotRunningRE = regexp.MustCompile(`Container .* is not running\n`) + + containerMetadata = report.MetadataTemplates{ + WeaveMACAddress: {ID: WeaveMACAddress, Label: "Weave MAC", From: report.FromLatest, Priority: 17}, + WeaveDNSHostname: {ID: WeaveDNSHostname, Label: "Weave DNS Name", From: report.FromLatest, Priority: 18}, + } + + weaveMetadata = report.MetadataTemplates{ + WeaveVersion: {ID: WeaveVersion, Label: "Version", From: report.FromLatest, Priority: 1}, + WeaveProtocol: {ID: WeaveProtocol, Label: "Protocol", From: report.FromLatest, Priority: 2}, + WeavePeerName: {ID: WeavePeerName, Label: "Name", From: report.FromLatest, Priority: 3}, + WeaveEncryption: {ID: WeaveEncryption, Label: "Encryption", From: report.FromLatest, Priority: 4}, + WeavePeerDiscovery: {ID: WeavePeerDiscovery, Label: "Peer Discovery", From: report.FromLatest, Priority: 5}, + WeaveTargetCount: {ID: WeaveTargetCount, Label: "Targets", From: report.FromLatest, Priority: 6}, + WeaveConnectionCount: {ID: WeaveConnectionCount, Label: "Connections", From: report.FromLatest, Priority: 8}, + WeavePeerCount: {ID: WeavePeerCount, Label: "Peers", From: report.FromLatest, Priority: 7}, + WeaveTrustedSubnets: {ID: WeaveTrustedSubnets, Label: "Trusted Subnets", From: report.FromSets, Priority: 9}, + } + + weaveTableTemplates = report.TableTemplates{ + WeaveIPAMTableID: {ID: WeaveIPAMTableID, Label: "IPAM", + FixedRows: map[string]string{ + WeaveIPAMStatus: "Status", + WeaveIPAMRange: "Range", + WeaveIPAMDefaultSubnet: "Default Subnet", + }, + }, + WeaveDNSTableID: {ID: WeaveDNSTableID, Label: "DNS", + FixedRows: map[string]string{ + WeaveDNSDomain: "Domain", + WeaveDNSUpstream: "Upstream", + WeaveDNSTTL: "TTL", + WeaveDNSEntryCount: "Entries", + }, + }, + WeaveProxyTableID: {ID: WeaveProxyTableID, Label: "Proxy", + FixedRows: map[string]string{ + WeaveProxyStatus: "Status", + WeaveProxyAddress: "Address", + }, + }, + WeavePluginTableID: {ID: WeavePluginTableID, Label: "Plugin", + FixedRows: map[string]string{ + WeavePluginStatus: "Status", + WeavePluginDriver: "Driver Name", + }, + }, + } +) + +// DockerClient is used for testing +type DockerClient interface { + CreateExec(docker_client.CreateExecOptions) (*docker_client.Exec, error) + StartExec(string, docker_client.StartExecOptions) error + InspectContainer(id string) (*docker_client.Container, error) +} + +func newDockerClient(endpoint string) (DockerClient, error) { + return docker_client.NewClient(endpoint) +} + // Weave represents a single Weave router, presumably on the same host // as the probe. It is both a Reporter and a Tagger: it produces an Overlay // topology, and (in theory) can tag existing topologies with foreign keys to // overlay -- though I'm not sure what that would look like in practice right // now. type Weave struct { - client weave.Client - hostID string + client weave.Client + dockerClient DockerClient + hostID string - mtx sync.RWMutex - statusCache weave.Status - psCache map[string]weave.PSEntry + mtx sync.RWMutex + statusCache weave.Status + psCache map[string]weave.PSEntry + proxyRunningCache bool + proxyAddressCache string + pluginRunningCache bool - backoff backoff.Interface - psBackoff backoff.Interface + backoff backoff.Interface + psBackoff backoff.Interface + proxyBackoff backoff.Interface + pluginBackoff backoff.Interface } // NewWeave returns a new Weave tagger based on the Weave router at // address. The address should be an IP or FQDN, no port. -func NewWeave(hostID string, client weave.Client) *Weave { +func NewWeave(hostID string, client weave.Client, dockerEndpoint string) (*Weave, error) { + dockerClient, err := NewDockerClientStub(dockerEndpoint) + if err != nil { + return nil, err + } w := &Weave{ - client: client, - hostID: hostID, - psCache: map[string]weave.PSEntry{}, + client: client, + dockerClient: dockerClient, + hostID: hostID, + psCache: map[string]weave.PSEntry{}, } w.backoff = backoff.New(w.status, "collecting weave status") @@ -66,7 +158,15 @@ func NewWeave(hostID string, client weave.Client) *Weave { w.psBackoff.SetInitialBackoff(10 * time.Second) go w.psBackoff.Start() - return w + w.proxyBackoff = backoff.New(w.proxyStatus, "collecting weave proxy status") + w.proxyBackoff.SetInitialBackoff(10 * time.Second) + go w.proxyBackoff.Start() + + w.pluginBackoff = backoff.New(w.pluginStatus, "collecting weave plugin status") + w.pluginBackoff.SetInitialBackoff(10 * time.Second) + go w.pluginBackoff.Start() + + return w, nil } // Name of this reporter/tagger/ticker, for metrics gathering @@ -76,6 +176,8 @@ func (*Weave) Name() string { return "Weave" } func (w *Weave) Stop() { w.backoff.Stop() w.psBackoff.Stop() + w.proxyBackoff.Stop() + w.pluginBackoff.Stop() } func (w *Weave) ps() (bool, error) { @@ -106,25 +208,99 @@ func (w *Weave) status() (bool, error) { return false, err } +func filterContainerNotFound(err error) error { + if err == nil { + return nil + } + + switch err.(type) { + case *docker_client.Error: + // This is really ugly, but this error comes from the client in some cases + // and there is no other way to distinguish it :( + dockerError := err.(*docker_client.Error) + if containerNotRunningRE.MatchString(dockerError.Message) { + return nil + } + case *docker_client.ContainerNotRunning: + return nil + case *docker_client.NoSuchContainer: + return nil + } + + return err +} + +func (w *Weave) proxyStatus() (bool, error) { + update := func(running bool, address string) { + w.mtx.Lock() + defer w.mtx.Unlock() + w.proxyRunningCache = running + w.proxyAddressCache = address + } + + exec, err := w.dockerClient.CreateExec(docker_client.CreateExecOptions{ + AttachStdout: true, + Cmd: []string{"curl", "-s", "--unix-socket", "status.sock", "http:/status"}, + Container: "weaveproxy", + }) + if err != nil { + update(false, "") + return false, filterContainerNotFound(err) + } + out := bytes.NewBuffer(nil) + err = w.dockerClient.StartExec(exec.ID, docker_client.StartExecOptions{ + OutputStream: out, + }) + if err != nil { + update(true, "") + return false, filterContainerNotFound(err) + } + + update(true, out.String()) + + return false, nil +} + +func (w *Weave) pluginStatus() (bool, error) { + update := func(running bool) { + w.mtx.Lock() + defer w.mtx.Unlock() + + w.pluginRunningCache = running + } + + c, err := w.dockerClient.InspectContainer("weaveplugin") + if err != nil { + update(false) + return false, filterContainerNotFound(err) + } + + update(c.State.Running) + + return false, nil +} + // Tag implements Tagger. func (w *Weave) Tag(r report.Report) (report.Report, error) { w.mtx.RLock() defer w.mtx.RUnlock() // Put information from weaveDNS on the container nodes - for _, entry := range w.statusCache.DNS.Entries { - if entry.Tombstone > 0 { - continue + if w.statusCache.DNS != nil { + for _, entry := range w.statusCache.DNS.Entries { + if entry.Tombstone > 0 { + continue + } + nodeID := report.MakeContainerNodeID(entry.ContainerID) + node, ok := r.Container.Nodes[nodeID] + if !ok { + continue + } + w, _ := node.Latest.Lookup(WeaveDNSHostname) + hostnames := report.IDList(strings.Fields(w)) + hostnames = hostnames.Add(strings.TrimSuffix(entry.Hostname, ".")) + r.Container.Nodes[nodeID] = node.WithLatests(map[string]string{WeaveDNSHostname: strings.Join(hostnames, " ")}) } - nodeID := report.MakeContainerNodeID(entry.ContainerID) - node, ok := r.Container.Nodes[nodeID] - if !ok { - continue - } - w, _ := node.Latest.Lookup(WeaveDNSHostname) - hostnames := report.IDList(strings.Fields(w)) - hostnames = hostnames.Add(strings.TrimSuffix(entry.Hostname, ".")) - r.Container.Nodes[nodeID] = node.WithLatests(map[string]string{WeaveDNSHostname: strings.Join(hostnames, " ")}) } // Put information from weave ps on the container nodes @@ -162,43 +338,132 @@ func (w *Weave) Report() (report.Report, error) { defer w.mtx.RUnlock() r := report.MakeReport() - r.Container = r.Container.WithMetadataTemplates(report.MetadataTemplates{ - WeaveMACAddress: {ID: WeaveMACAddress, Label: "Weave MAC", From: report.FromLatest, Priority: 17}, - WeaveDNSHostname: {ID: WeaveDNSHostname, Label: "Weave DNS Name", From: report.FromLatest, Priority: 18}, - }) - 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}, - }) + r.Container = r.Container.WithMetadataTemplates(containerMetadata) + r.Overlay = r.Overlay.WithMetadataTemplates(weaveMetadata).WithTableTemplates(weaveTableTemplates) // 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)) - } - } + node := w.getPeerNode(peer) r.Overlay.AddNode(node) } - if w.statusCache.IPAM.DefaultSubnet != "" { + if w.statusCache.IPAM != nil { r.Overlay.AddNode( - report.MakeNode(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, w.statusCache.Router.Name)).WithSets( - report.MakeSets().Add(host.LocalNetworks, report.MakeStringSet(w.statusCache.IPAM.DefaultSubnet)), - ), + report.MakeNode(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, w.statusCache.Router.Name)). + WithSet(host.LocalNetworks, report.MakeStringSet(w.statusCache.IPAM.DefaultSubnet)), ) } return r, nil } + +// getPeerNode obtains an Overlay topology node for representing a peer in the Weave network +func (w *Weave) getPeerNode(peer weave.Peer) report.Node { + node := report.MakeNode(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, peer.Name)) + latests := map[string]string{ + WeavePeerName: peer.Name, + WeavePeerNickName: peer.NickName, + } + + // Peer corresponding to current host + if peer.Name == w.statusCache.Router.Name { + latests, node = w.addCurrentPeerInfo(latests, node) + } + + for _, conn := range peer.Connections { + if conn.Outbound { + node = node.WithAdjacent(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, conn.Name)) + } + } + + return node.WithLatests(latests) +} + +// addCurrentPeerInfo adds information exclusive to the Overlay topology node representing current Weave Net peer +// (i.e. in the same host as the reporting Scope probe) +func (w *Weave) addCurrentPeerInfo(latests map[string]string, node report.Node) (map[string]string, report.Node) { + latests[report.HostNodeID] = w.hostID + latests[WeaveVersion] = w.statusCache.Version + latests[WeaveEncryption] = "disabled" + if w.statusCache.Router.Encryption { + latests[WeaveEncryption] = "enabled" + } + latests[WeavePeerDiscovery] = "disabled" + if w.statusCache.Router.PeerDiscovery { + latests[WeavePeerDiscovery] = "enabled" + } + if w.statusCache.Router.ProtocolMinVersion == w.statusCache.Router.ProtocolMaxVersion { + latests[WeaveProtocol] = fmt.Sprintf("%d", w.statusCache.Router.ProtocolMinVersion) + } else { + latests[WeaveProtocol] = fmt.Sprintf("%d..%d", w.statusCache.Router.ProtocolMinVersion, w.statusCache.Router.ProtocolMaxVersion) + } + latests[WeaveTargetCount] = fmt.Sprintf("%d", len(w.statusCache.Router.Targets)) + latests[WeaveConnectionCount] = fmt.Sprintf("%d", len(w.statusCache.Router.Connections)) + latests[WeavePeerCount] = fmt.Sprintf("%d", len(w.statusCache.Router.Peers)) + node = node.WithSet(WeaveTrustedSubnets, report.MakeStringSet(w.statusCache.Router.TrustedSubnets...)) + if w.statusCache.IPAM != nil { + latests[WeaveIPAMStatus] = getIPAMStatus(*w.statusCache.IPAM) + latests[WeaveIPAMRange] = w.statusCache.IPAM.Range + latests[WeaveIPAMDefaultSubnet] = w.statusCache.IPAM.DefaultSubnet + } + if w.statusCache.DNS != nil { + latests[WeaveDNSDomain] = w.statusCache.DNS.Domain + latests[WeaveDNSUpstream] = strings.Join(w.statusCache.DNS.Upstream, ", ") + latests[WeaveDNSTTL] = fmt.Sprintf("%d", w.statusCache.DNS.TTL) + dnsEntryCount := 0 + for _, entry := range w.statusCache.DNS.Entries { + if entry.Tombstone == 0 { + dnsEntryCount++ + } + } + latests[WeaveDNSEntryCount] = fmt.Sprintf("%d", dnsEntryCount) + } + latests[WeaveProxyStatus] = "not running" + if w.proxyRunningCache { + latests[WeaveProxyStatus] = "running" + latests[WeaveProxyAddress] = w.proxyAddressCache + } + latests[WeavePluginStatus] = "not running" + if w.pluginRunningCache { + latests[WeavePluginStatus] = "running" + latests[WeavePluginDriver] = "weave" + } + node = node.WithParents(report.EmptySets.Add(report.Host, report.MakeStringSet(w.hostID))) + + return latests, node +} + +func getIPAMStatus(ipam weave.IPAM) string { + allIPAMOwnersUnreachable := func(ipam weave.IPAM) bool { + for _, entry := range ipam.Entries { + if entry.Size > 0 && entry.IsKnownPeer { + return false + } + } + return true + } + + if len(ipam.Entries) > 0 { + if allIPAMOwnersUnreachable(ipam) { + return "all ranges owned by unreachable peers" + } else if len(ipam.PendingAllocates) > 0 { + return "waiting for grant" + + } else { + return "ready" + } + } + + if ipam.Paxos != nil { + if ipam.Paxos.Elector { + return fmt.Sprintf( + "awaiting consensus (quorum: %d, known: %d)", + ipam.Paxos.Quorum, + ipam.Paxos.KnownNodes, + ) + } + return "priming" + } + + return "idle" +} diff --git a/probe/overlay/weave_test.go b/probe/overlay/weave_test.go index aec56548b..78afe2434 100644 --- a/probe/overlay/weave_test.go +++ b/probe/overlay/weave_test.go @@ -1,6 +1,7 @@ package overlay_test import ( + "sync" "testing" "time" @@ -11,6 +12,8 @@ import ( "github.com/weaveworks/scope/test" "github.com/weaveworks/scope/test/reflect" "github.com/weaveworks/scope/test/weave" + + docker_client "github.com/fsouza/go-dockerclient" ) const ( @@ -18,8 +21,58 @@ const ( mockContainerIPWithScope = ";" + weave.MockContainerIP ) -func TestWeaveTaggerOverlayTopology(t *testing.T) { - w := overlay.NewWeave(mockHostID, weave.MockClient{}) +type mockDockerClient struct { + sync.RWMutex + containers map[string]*docker_client.Container +} + +func (m *mockDockerClient) InspectContainer(id string) (*docker_client.Container, error) { + m.RLock() + defer m.RUnlock() + c, ok := m.containers[id] + if !ok { + return nil, &docker_client.NoSuchContainer{} + } + return c, nil +} + +func (m *mockDockerClient) CreateExec(docker_client.CreateExecOptions) (*docker_client.Exec, error) { + return &docker_client.Exec{ID: "id"}, nil +} + +func (m *mockDockerClient) StartExec(_ string, options docker_client.StartExecOptions) error { + options.OutputStream.Write([]byte("exec_output")) + return nil +} + +var mockWeaveContainers = map[string]*docker_client.Container{ + "weaveproxy": { + ID: "foo", + State: docker_client.State{ + Running: true, + }, + }, + + "weaveplugin": { + ID: "bar", + State: docker_client.State{ + Running: true, + }, + }, +} + +func runTest(t *testing.T, f func(*overlay.Weave)) { + // Place and restore docker client + origNewDockerClientStub := overlay.NewDockerClientStub + overlay.NewDockerClientStub = func(string) (overlay.DockerClient, error) { + return &mockDockerClient{containers: mockWeaveContainers}, nil + } + defer func() { overlay.NewDockerClientStub = origNewDockerClientStub }() + + w, err := overlay.NewWeave(mockHostID, weave.MockClient{}, "") + if err != nil { + t.Fatal(err) + } defer w.Stop() // Wait until the reporter reports some nodes @@ -28,30 +81,11 @@ func TestWeaveTaggerOverlayTopology(t *testing.T) { return len(have.Overlay.Nodes) }) - { - // Overlay node should include peer name and nickname - have, err := w.Report() - if err != nil { - t.Fatal(err) - } + f(w) +} - 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) - } - if peerName, ok := node.Latest.Lookup(overlay.WeavePeerName); !ok || peerName != weave.MockWeavePeerName { - t.Errorf("Expected weave peer name %q, got %q", weave.MockWeavePeerName, peerName) - } - if peerNick, ok := node.Latest.Lookup(overlay.WeavePeerNickName); !ok || peerNick != weave.MockWeavePeerNickName { - t.Errorf("Expected weave peer nickname %q, got %q", weave.MockWeavePeerNickName, peerNick) - } - if localNetworks, ok := node.Sets.Lookup(host.LocalNetworks); !ok || !reflect.DeepEqual(localNetworks, report.MakeStringSet(weave.MockWeaveDefaultSubnet)) { - t.Errorf("Expected weave node local_networks %q, got %q", report.MakeStringSet(weave.MockWeaveDefaultSubnet), localNetworks) - } - } - - { +func TestContainerTopologyTagging(t *testing.T) { + test := func(w *overlay.Weave) { // Container nodes should be tagged with their overlay info nodeID := report.MakeContainerNodeID(weave.MockContainerID) have, err := w.Tag(report.Report{ @@ -85,4 +119,49 @@ func TestWeaveTaggerOverlayTopology(t *testing.T) { t.Errorf("Expected container ips to include the weave IP (with scope) %q, got %q", mockContainerIPWithScope, have) } } + + runTest(t, test) +} + +func TestOverlayTopology(t *testing.T) { + test := func(w *overlay.Weave) { + // Overlay node should include peer name and nickname + have, err := w.Report() + if err != nil { + t.Fatal(err) + } + + 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) + } + if peerName, ok := node.Latest.Lookup(overlay.WeavePeerName); !ok || peerName != weave.MockWeavePeerName { + t.Errorf("Expected weave peer name %q, got %q", weave.MockWeavePeerName, peerName) + } + if peerNick, ok := node.Latest.Lookup(overlay.WeavePeerNickName); !ok || peerNick != weave.MockWeavePeerNickName { + t.Errorf("Expected weave peer nickname %q, got %q", weave.MockWeavePeerNickName, peerNick) + } + if localNetworks, ok := node.Sets.Lookup(host.LocalNetworks); !ok || !reflect.DeepEqual(localNetworks, report.MakeStringSet(weave.MockWeaveDefaultSubnet)) { + t.Errorf("Expected weave node local_networks %q, got %q", report.MakeStringSet(weave.MockWeaveDefaultSubnet), localNetworks) + } + // The weave proxy container is running + if have, ok := node.Latest.Lookup(overlay.WeaveProxyStatus); !ok || have != "running" { + t.Errorf("Expected weave proxy status %q, got %q", "running", have) + } + // The weave proxy address should equal what Exec writes to stdout + if have, ok := node.Latest.Lookup(overlay.WeaveProxyAddress); !ok || have != "exec_output" { + t.Errorf("Expected weave proxy address %q, got %q", "exec_output", have) + } + // The weave plugin container is running + if have, ok := node.Latest.Lookup(overlay.WeavePluginStatus); !ok || have != "running" { + t.Errorf("Expected weave plugin status %q, got %q", "running", have) + } + // The mock data indicates ranges are owned by unreachable peers + if have, ok := node.Latest.Lookup(overlay.WeaveIPAMStatus); !ok || have != "all ranges owned by unreachable peers" { + t.Errorf("Expected weave IPAM status %q, got %q", "all ranges owned by unreachable peers", have) + } + } + + runTest(t, test) } diff --git a/prog/probe.go b/prog/probe.go index 4e31c510d..5d638ea81 100644 --- a/prog/probe.go +++ b/prog/probe.go @@ -40,7 +40,10 @@ const ( defaultServiceHost = "https://cloud.weave.works:443" ) -var pluginAPIVersion = "1" +var ( + pluginAPIVersion = "1" + dockerEndpoint = "unix:///var/run/docker.sock" +) func check(flags map[string]string) { handleResponse := func(r *checkpoint.CheckResponse, err error) { @@ -174,7 +177,7 @@ func probeMain(flags probeFlags, targets []appclient.Target) { log.Errorf("Docker: problem with bridge %s: %v", flags.dockerBridge, err) } } - if registry, err := docker.NewRegistry(flags.dockerInterval, clients, true, hostID, handlerRegistry); err == nil { + if registry, err := docker.NewRegistry(flags.dockerInterval, clients, true, hostID, handlerRegistry, dockerEndpoint); err == nil { defer registry.Stop() if flags.procEnabled { p.AddTagger(docker.NewTagger(registry, processCache)) @@ -200,29 +203,33 @@ func probeMain(flags probeFlags, targets []appclient.Target) { if flags.weaveEnabled { client := weave.NewClient(sanitize.URL("http://", 6784, "")(flags.weaveAddr)) - weave := overlay.NewWeave(hostID, client) - defer weave.Stop() - p.AddTagger(weave) - p.AddReporter(weave) - - dockerBridgeIP, err := network.GetFirstAddressOf(flags.dockerBridge) + weave, err := overlay.NewWeave(hostID, client, dockerEndpoint) if err != nil { - log.Println("Error getting docker bridge ip:", err) + log.Errorf("Weave: failed to start client: %v", err) } else { - weaveDNSLookup := appclient.LookupUsing(dockerBridgeIP + ":53") - weaveTargets, err := appclient.ParseTargets([]string{flags.weaveHostname}) + defer weave.Stop() + p.AddTagger(weave) + p.AddReporter(weave) + + dockerBridgeIP, err := network.GetFirstAddressOf(flags.dockerBridge) if err != nil { - log.Errorf("Failed to parse weave targets: %v", err) + log.Errorf("Error getting docker bridge ip: %v", err) } else { - weaveResolver, err := appclient.NewResolver(appclient.ResolverConfig{ - Targets: weaveTargets, - Lookup: weaveDNSLookup, - Set: clients.Set, - }) + weaveDNSLookup := appclient.LookupUsing(dockerBridgeIP + ":53") + weaveTargets, err := appclient.ParseTargets([]string{flags.weaveHostname}) if err != nil { - log.Errorf("Failed to create weave resolver: %v", err) + log.Errorf("Failed to parse weave targets: %v", err) } else { - defer weaveResolver.Stop() + weaveResolver, err := appclient.NewResolver(appclient.ResolverConfig{ + Targets: weaveTargets, + Lookup: weaveDNSLookup, + Set: clients.Set, + }) + if err != nil { + log.Errorf("Failed to create weave resolver: %v", err) + } else { + defer weaveResolver.Stop() + } } } } diff --git a/test/weave/mock.go b/test/weave/mock.go index ff59c9a4b..c5de9e4ab 100644 --- a/test/weave/mock.go +++ b/test/weave/mock.go @@ -25,24 +25,14 @@ func (MockClient) Status() (weave.Status, error) { return weave.Status{ Router: weave.Router{ Name: MockWeavePeerName, - Peers: []struct { - Name string - NickName string - Connections []struct { - Name string - NickName string - Address string - Outbound bool - Established bool - } - }{ + Peers: []weave.Peer{ { Name: MockWeavePeerName, NickName: MockWeavePeerNickName, }, }, }, - DNS: weave.DNS{ + DNS: &weave.DNS{ Entries: []struct { Hostname string ContainerID string @@ -55,8 +45,14 @@ func (MockClient) Status() (weave.Status, error) { }, }, }, - IPAM: weave.IPAM{ + IPAM: &weave.IPAM{ DefaultSubnet: MockWeaveDefaultSubnet, + Entries: []struct { + Size uint32 + IsKnownPeer bool + }{ + {Size: 0, IsKnownPeer: false}, + }, }, }, nil }