Skip to content

Commit

Permalink
Merge pull request #254 from tomwilkie/230-container-image-topology
Browse files Browse the repository at this point in the history
Add ContainerImage topology and use it to build the Container By Image graph.
  • Loading branch information
tomwilkie committed Jun 18, 2015
2 parents e9ae65b + 25ca0c0 commit a6ef295
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 79 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
49 changes: 33 additions & 16 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 @@ -67,18 +68,31 @@ func TestDockerTagger(t *testing.T) {
}

var (
pid1NodeID = report.MakeProcessNodeID("somehost.com", "1")
pid2NodeID = report.MakeProcessNodeID("somehost.com", "2")
endpointNodeMetadata = report.NodeMetadata{
pid1NodeID = report.MakeProcessNodeID("somehost.com", "1")
pid2NodeID = report.MakeProcessNodeID("somehost.com", "2")
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 a6ef295

Please sign in to comment.