Skip to content

Commit

Permalink
Add ContainerImage topology and use it to build the Container By Imag…
Browse files Browse the repository at this point in the history
…e graph.

This makes container image details show the containers (and processes) correctly.

Also:
- introduces a 'test' package, moved Diff function there.
- adds some tests for this new rendered view.
  • Loading branch information
Tom Wilkie committed Jun 17, 2015
1 parent 5d7c860 commit ea855d2
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 77 deletions.
2 changes: 1 addition & 1 deletion app/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var topologyRegistry = map[string]topologyView{
"containers-by-image": {
human: "by image",
parent: "containers",
renderer: render.LeafMap{Selector: report.SelectEndpoint, Mapper: render.ProcessContainerImage, Pseudo: render.InternetOnlyPseudoNode},
renderer: render.ContainerImageRenderer,
},
"hosts": {
human: "Hosts",
Expand Down
1 change: 1 addition & 0 deletions probe/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func main() {

if dockerTagger != nil {
r.Container.Merge(dockerTagger.ContainerTopology(hostID))
r.ContainerImage.Merge(dockerTagger.ContainerImageTopology(hostID))
}

if weaveTagger != nil {
Expand Down
35 changes: 22 additions & 13 deletions probe/tag/docker_tagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,6 @@ func (t *DockerTagger) Containers() []*docker.Container {
// Tag implements Tagger.
func (t *DockerTagger) Tag(r report.Report) report.Report {
t.tag(&r.Process)
t.tag(&r.Endpoint)
return r
}

Expand Down Expand Up @@ -313,15 +312,6 @@ func (t *DockerTagger) tag(topology *report.Topology) {

md := report.NodeMetadata{
ContainerID: container.ID,
ImageID: container.Image,
}

t.RLock()
image, ok := t.images[container.Image]
t.RUnlock()

if ok && len(image.RepoTags) > 0 {
md[ImageName] = image.RepoTags[0]
}

topology.NodeMetadatas[nodeID].Merge(md)
Expand All @@ -341,14 +331,33 @@ func (t *DockerTagger) ContainerTopology(scope string) report.Topology {
ImageID: container.Image,
}

nmd.Merge(container.getStats())

nodeID := report.MakeContainerNodeID(scope, container.ID)
result.NodeMetadatas[nodeID] = nmd
}
return result
}

// ContainerImageTopology produces a Toplogy of Container Images
func (t *DockerTagger) ContainerImageTopology(scope string) report.Topology {
t.RLock()
defer t.RUnlock()

result := report.NewTopology()

// Loop over containers so we only emit images for running containers.
for _, container := range t.containers {
nmd := report.NodeMetadata{
ImageID: container.Image,
}

image, ok := t.images[container.Image]
if ok && len(image.RepoTags) > 0 {
nmd[ImageName] = image.RepoTags[0]
}

nmd.Merge(container.getStats())

nodeID := report.MakeContainerNodeID(scope, container.ID)
nodeID := report.MakeContainerNodeID(scope, container.Image)
result.NodeMetadatas[nodeID] = nmd
}
return result
Expand Down
45 changes: 31 additions & 14 deletions probe/tag/docker_tagger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

docker "github.com/fsouza/go-dockerclient"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)

type mockDockerClient struct {
Expand Down Expand Up @@ -69,16 +70,29 @@ func TestDockerTagger(t *testing.T) {
var (
pid1NodeID = report.MakeProcessNodeID("somehost.com", "1")
pid2NodeID = report.MakeProcessNodeID("somehost.com", "2")
endpointNodeMetadata = report.NodeMetadata{
processNodeMetadata = report.NodeMetadata{
ContainerID: "foo",
ImageID: "baz",
ImageName: "bang",
}
processNodeMetadata = report.NodeMetadata{
ContainerID: "foo",
ContainerName: "bar",
ImageID: "baz",
ImageName: "bang",
wantContainerTopology = report.Topology{
Adjacency: report.Adjacency{},
EdgeMetadatas: report.EdgeMetadatas{},
NodeMetadatas: report.NodeMetadatas{
report.MakeContainerNodeID("", "foo"): report.NodeMetadata{
ContainerID: "foo",
ContainerName: "bar",
ImageID: "baz",
},
},
}
wantContainerImageTopology = report.Topology{
Adjacency: report.Adjacency{},
EdgeMetadatas: report.EdgeMetadatas{},
NodeMetadatas: report.NodeMetadatas{
report.MakeContainerNodeID("", "baz"): report.NodeMetadata{
ImageID: "baz",
ImageName: "bang",
},
},
}
)

Expand All @@ -89,18 +103,21 @@ func TestDockerTagger(t *testing.T) {
dockerTagger, _ := NewDockerTagger("/irrelevant", 10*time.Second)
runtime.Gosched()
for _, nodeID := range []string{pid1NodeID, pid2NodeID} {
want := endpointNodeMetadata.Copy()
want := processNodeMetadata.Copy()
have := dockerTagger.Tag(r).Process.NodeMetadatas[nodeID].Copy()
delete(have, "pid")
if !reflect.DeepEqual(want, have) {
t.Errorf("%q: want %+v, have %+v", nodeID, 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)
haveContainerTopology := dockerTagger.ContainerTopology("")
if !reflect.DeepEqual(wantContainerTopology, haveContainerTopology) {
t.Errorf("%s", test.Diff(wantContainerTopology, haveContainerTopology))
}

haveContainerImageTopology := dockerTagger.ContainerImageTopology("")
if !reflect.DeepEqual(wantContainerImageTopology, haveContainerImageTopology) {
t.Errorf("%s", test.Diff(wantContainerImageTopology, haveContainerImageTopology))
}
}
21 changes: 20 additions & 1 deletion render/detailed_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ func OriginTable(r report.Report, originID string) (Table, bool) {
if nmd, ok := r.Container.NodeMetadatas[originID]; ok {
return containerOriginTable(nmd)
}
if nmd, ok := r.ContainerImage.NodeMetadatas[originID]; ok {
return containerImageOriginTable(nmd)
}
if nmd, ok := r.Host.NodeMetadatas[originID]; ok {
return hostOriginTable(nmd)
}
Expand Down Expand Up @@ -155,7 +158,6 @@ func containerOriginTable(nmd report.NodeMetadata) (Table, bool) {
{"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: ""})
Expand All @@ -168,6 +170,23 @@ func containerOriginTable(nmd report.NodeMetadata) (Table, bool) {
}, len(rows) > 0
}

func containerImageOriginTable(nmd report.NodeMetadata) (Table, bool) {
rows := []Row{}
for _, tuple := range []struct{ key, human string }{
{"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 Image",
Numeric: false,
Rows: rows,
}, len(rows) > 0
}

func hostOriginTable(nmd report.NodeMetadata) (Table, bool) {
rows := []Row{}
if val, ok := nmd["host_name"]; ok {
Expand Down
6 changes: 4 additions & 2 deletions render/detailed_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/test"
)

func TestOriginTable(t *testing.T) {
Expand Down Expand Up @@ -51,7 +52,7 @@ func TestOriginTable(t *testing.T) {
continue
}
if !reflect.DeepEqual(want, have) {
t.Errorf("%q: %s", originID, diff(want, have))
t.Errorf("%q: %s", originID, test.Diff(want, have))
}
}
}
Expand Down Expand Up @@ -95,6 +96,7 @@ func TestMakeDetailedNode(t *testing.T) {
Rows: []render.Row{
{"Container ID", "5e4d3c2b1a", ""},
{"Container name", "server", ""},
{"Container image ID", "imageid456", ""},
},
},
{
Expand All @@ -109,6 +111,6 @@ func TestMakeDetailedNode(t *testing.T) {
},
}
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", diff(want, have))
t.Errorf("%s", test.Diff(want, have))
}
}
39 changes: 29 additions & 10 deletions render/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,19 @@ func MapContainerIdentity(m report.NodeMetadata) (RenderableNode, bool) {
return NewRenderableNode(id, major, minor, rank, m), true
}

// MapContainerImageIdentity maps a container image topology node to container
// image RenderableNode node. As it is only ever run on container image
// topology nodes, we can safely assume the presences of certain keys.
func MapContainerImageIdentity(m report.NodeMetadata) (RenderableNode, bool) {
var (
id = m["docker_image_id"]
major = m["docker_image_name"]
rank = m["docker_image_id"]
)

return NewRenderableNode(id, major, "", rank, m), true
}

// MapEndpoint2Process maps endpoint RenderableNodes to process
// RenderableNodes.
//
Expand Down Expand Up @@ -156,18 +169,24 @@ func MapProcess2Name(n RenderableNode) (RenderableNode, bool) {
return node, true
}

// ProcessContainerImage maps topology nodes to the container images they run
// on. If no container metadata is found, nodes are grouped into the
// Uncontained node.
func ProcessContainerImage(m report.NodeMetadata) (RenderableNode, bool) {
var id, major, minor, rank string
if m["docker_image_id"] == "" {
id, major, minor, rank = UncontainedID, UncontainedMajor, "", UncontainedID
} else {
id, major, minor, rank = m["docker_image_id"], m["docker_image_name"], "", m["docker_image_id"]
// MapContainer2ContainerImage maps container RenderableNodes to container
// image RenderableNodes.
//
// If this function is given a node without a docker_image_id
// (including other pseudo nodes), it will produce an "Uncontained"
// pseudo node.
//
// Otherwise, this function will produce a node with the correct ID
// format for a container, but without any Major or Minor labels.
// It does not have enough info to do that, and the resulting graph
// must be merged with a container graph to get that info.
func MapContainer2ContainerImage(n RenderableNode) (RenderableNode, bool) {
id, ok := n.NodeMetadata["docker_image_id"]
if !ok || n.Pseudo {
return newDerivedPseudoNode(UncontainedID, UncontainedMajor, n), true
}

return NewRenderableNode(id, major, minor, rank, m), true
return newDerivedNode(id, n), true
}

// NetworkHostname takes a node NodeMetadata and returns a representation
Expand Down
14 changes: 14 additions & 0 deletions render/topologies.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,17 @@ var ContainerRenderer = MakeReduce(
Pseudo: GenericPseudoNode,
},
)

// ContainerImageRenderer is a Renderer which produces a renderable container
// image graph by merging the container graph and the container image topology.
var ContainerImageRenderer = MakeReduce(
Map{
MapFunc: MapContainer2ContainerImage,
Renderer: ContainerRenderer,
},
LeafMap{
Selector: report.SelectContainerImage,
Mapper: MapContainerImageIdentity,
Pseudo: GenericPseudoNode,
},
)
Loading

0 comments on commit ea855d2

Please sign in to comment.