From ff1102286291d981d41f11d537029dec55cf9145 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 10 Jun 2015 15:11:32 +0000 Subject: [PATCH] Add Containers topology populated by the Docker tagger. --- probe/main.go | 6 +++++ probe/tag/docker_tagger.go | 41 +++++++++++++++++++++++++++++---- probe/tag/docker_tagger_test.go | 15 ++++++++---- report/detailed_node.go | 23 +++++++++++++++++- report/id.go | 5 ++++ report/merge.go | 1 + report/report.go | 15 ++++++++---- 7 files changed, 93 insertions(+), 13 deletions(-) diff --git a/probe/main.go b/probe/main.go index 29bf52546..87221ea2a 100644 --- a/probe/main.go +++ b/probe/main.go @@ -99,11 +99,17 @@ func main() { case <-spyTick: r.Merge(spy(hostID, hostName, *spyProcs)) + if pidTree, err := tag.NewPIDTree(*procRoot); err == nil { r.Process.Merge(pidTree.ProcessTopology(hostID)) } else { log.Print(err) } + + if dockerTagger != nil { + r.Container.Merge(dockerTagger.ContainerTopology(hostID)) + } + r = tag.Apply(r, taggers) case <-quit: diff --git a/probe/tag/docker_tagger.go b/probe/tag/docker_tagger.go index 2729f3bd3..9097ecdf8 100644 --- a/probe/tag/docker_tagger.go +++ b/probe/tag/docker_tagger.go @@ -16,6 +16,15 @@ const ( start = "start" ) +// These constants are keys used in node metadata +// TODO: use these constants in report/{mapping.go, detailed_node.go} - pending some circular references +const ( + ContainerID = "docker_container_id" + ContainerName = "docker_container_name" + ImageID = "docker_image_id" + ImageName = "docker_image_name" +) + var ( newDockerClientStub = newDockerClient newPIDTreeStub = NewPIDTree @@ -290,9 +299,9 @@ func (t *DockerTagger) Tag(r report.Report) report.Report { } md := report.NodeMetadata{ - "docker_container_id": container.ID, - "docker_container_name": strings.TrimPrefix(container.Name, "/"), - "docker_image_id": container.Image, + ContainerID: container.ID, + ContainerName: strings.TrimPrefix(container.Name, "/"), + ImageID: container.Image, } t.RLock() @@ -300,7 +309,7 @@ func (t *DockerTagger) Tag(r report.Report) report.Report { t.RUnlock() if ok && len(image.RepoTags) > 0 { - md["docker_image_name"] = image.RepoTags[0] + md[ImageName] = image.RepoTags[0] } r.Endpoint.NodeMetadatas[nodeID].Merge(md) @@ -308,3 +317,27 @@ func (t *DockerTagger) Tag(r report.Report) report.Report { return r } + +// ContainerTopology produces a Toplogy of Containers +func (t *DockerTagger) ContainerTopology(scope string) report.Topology { + t.RLock() + defer t.RUnlock() + + result := report.NewTopology() + for _, container := range t.containers { + nmd := report.NodeMetadata{ + ContainerID: container.ID, + ContainerName: strings.TrimPrefix(container.Name, "/"), + ImageID: container.Image, + } + + image, ok := t.images[container.Image] + if ok && len(image.RepoTags) > 0 { + nmd[ImageName] = image.RepoTags[0] + } + + nodeID := report.MakeContainerNodeID(scope, container.ID) + result.NodeMetadatas[nodeID] = nmd + } + return result +} diff --git a/probe/tag/docker_tagger_test.go b/probe/tag/docker_tagger_test.go index ef02b15fe..e66dc6652 100644 --- a/probe/tag/docker_tagger_test.go +++ b/probe/tag/docker_tagger_test.go @@ -70,10 +70,10 @@ func TestDockerTagger(t *testing.T) { endpoint1NodeID = "somehost.com;192.168.1.1;12345" endpoint2NodeID = "somehost.com;192.168.1.1;67890" processNodeMetadata = report.NodeMetadata{ - "docker_container_id": "foo", - "docker_container_name": "bar", - "docker_image_id": "baz", - "docker_image_name": "bang", + ContainerID: "foo", + ContainerName: "bar", + ImageID: "baz", + ImageName: "bang", } ) @@ -91,4 +91,11 @@ func TestDockerTagger(t *testing.T) { t.Errorf("%q: want %+v, have %+v", endpointNodeID, want, have) } } + + wantTopology := report.NewTopology() + wantTopology.NodeMetadatas[report.MakeContainerNodeID("", "foo")] = processNodeMetadata + haveTopology := dockerTagger.ContainerTopology("") + if !reflect.DeepEqual(wantTopology, haveTopology) { + t.Errorf("toplog want %+v, have %+v", wantTopology, haveTopology) + } } diff --git a/report/detailed_node.go b/report/detailed_node.go index 1490176ac..e3df577d5 100644 --- a/report/detailed_node.go +++ b/report/detailed_node.go @@ -55,6 +55,9 @@ func OriginTable(r Report, originID string) (Table, bool) { if nmd, ok := r.Process.NodeMetadatas[originID]; ok { return processOriginTable(nmd) } + if nmd, ok := r.Container.NodeMetadatas[originID]; ok { + return containerOriginTable(nmd) + } if nmd, ok := r.Host.NodeMetadatas[originID]; ok { return hostOriginTable(nmd) } @@ -72,7 +75,6 @@ func endpointOriginTable(nmd NodeMetadata) (Table, bool) { {"docker_container_name", "Container name"}, {"docker_image_id", "Container image ID"}, {"docker_image_name", "Container image name"}, - {"cgroup", "cgroup"}, } { if val, ok := nmd[tuple.key]; ok { rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) @@ -118,6 +120,25 @@ func processOriginTable(nmd NodeMetadata) (Table, bool) { }, len(rows) > 0 } +func containerOriginTable(nmd NodeMetadata) (Table, bool) { + rows := []Row{} + for _, tuple := range []struct{ key, human string }{ + {"docker_container_id", "Container ID"}, + {"docker_container_name", "Container name"}, + {"docker_image_id", "Container image ID"}, + {"docker_image_name", "Container image name"}, + } { + if val, ok := nmd[tuple.key]; ok { + rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""}) + } + } + return Table{ + Title: "Origin Container", + Numeric: false, + Rows: rows, + }, len(rows) > 0 +} + func hostOriginTable(nmd NodeMetadata) (Table, bool) { rows := []Row{} if val, ok := nmd["host_name"]; ok { diff --git a/report/id.go b/report/id.go index f94cc5a46..249ed15fb 100644 --- a/report/id.go +++ b/report/id.go @@ -72,6 +72,11 @@ func MakeHostNodeID(hostID string) string { return hostID + ScopeDelim + "" } +// MakeContainerNodeID produces a container node ID from its composite parts. +func MakeContainerNodeID(hostID, containerID string) string { + return hostID + ScopeDelim + containerID +} + // ParseNodeID produces the scope and remainder from a node ID func ParseNodeID(nodeID string) (string, string, bool) { fields := strings.SplitN(nodeID, ScopeDelim, 2) diff --git a/report/merge.go b/report/merge.go index 8117af21d..2ef6f1fc9 100644 --- a/report/merge.go +++ b/report/merge.go @@ -9,6 +9,7 @@ func (r *Report) Merge(other Report) { r.Endpoint.Merge(other.Endpoint) r.Address.Merge(other.Address) r.Process.Merge(other.Process) + r.Container.Merge(other.Container) r.Host.Merge(other.Host) } diff --git a/report/report.go b/report/report.go index a20b8e5de..460e19f4d 100644 --- a/report/report.go +++ b/report/report.go @@ -22,6 +22,11 @@ type Report struct { // Process nodes are processes on each host. Edges are not present. Process Topology + // Container nodes represent all Docker containers on hosts running probes. + // Metadata includes things like Docker image, name etc. + // Edges are not present. + Container Topology + // Host nodes are physical hosts that run probes. Metadata includes things // like operating system, load, etc. The information is scraped by the // probes with each published report. Edges are not present. @@ -70,10 +75,11 @@ type Row struct { // MakeReport makes a clean report, ready to Merge() other reports into. func MakeReport() Report { return Report{ - Endpoint: NewTopology(), - Address: NewTopology(), - Process: NewTopology(), - Host: NewTopology(), + Endpoint: NewTopology(), + Address: NewTopology(), + Process: NewTopology(), + Container: NewTopology(), + Host: NewTopology(), } } @@ -84,6 +90,7 @@ func (r Report) Squash() Report { r.Endpoint = r.Endpoint.Squash(EndpointIDAddresser, localNetworks) r.Address = r.Address.Squash(AddressIDAddresser, localNetworks) r.Process = r.Process.Squash(PanicIDAddresser, localNetworks) + r.Container = r.Container.Squash(PanicIDAddresser, localNetworks) r.Host = r.Host.Squash(PanicIDAddresser, localNetworks) return r }