Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add very basic Docker Swarm service topology #2444

Merged
merged 3 commits into from
Apr 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/api_topologies.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
weaveID = "weave"
ecsTasksID = "ecs-tasks"
ecsServicesID = "ecs-services"
swarmServicesID = "swarm-services"
)

var (
Expand Down Expand Up @@ -252,6 +253,14 @@ func MakeRegistry() *Registry {
Options: []APITopologyOptionGroup{unmanagedFilter},
HideIfEmpty: true,
},
APITopologyDesc{
id: swarmServicesID,
renderer: render.SwarmServiceRenderer,
Name: "services",
Rank: 3,
Options: []APITopologyOptionGroup{unmanagedFilter},
HideIfEmpty: true,
},
APITopologyDesc{
id: hostsID,
renderer: render.HostRenderer,
Expand Down
8 changes: 4 additions & 4 deletions app/api_topologies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestAPITopology(t *testing.T) {
if err := decoder.Decode(&topologies); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
equals(t, 5, len(topologies))
equals(t, 6, len(topologies))

for _, topology := range topologies {
is200(t, ts, topology.URL)
Expand All @@ -50,7 +50,7 @@ func TestAPITopology(t *testing.T) {
}

// TODO: add ECS nodes in report fixture

This comment was marked as abuse.

if topology.Name == "Tasks" {
if topology.Name == "Tasks" || topology.Name == "services" {
continue
}

Expand Down Expand Up @@ -200,7 +200,7 @@ func TestAPITopologyAddsKubernetes(t *testing.T) {
if err := decoder.Decode(&topologies); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
equals(t, 5, len(topologies))
equals(t, 6, len(topologies))

// Enable the kubernetes topologies
rpt := report.MakeReport()
Expand Down Expand Up @@ -234,7 +234,7 @@ func TestAPITopologyAddsKubernetes(t *testing.T) {
if err := decoder.Decode(&topologies); err != nil {
t.Fatalf("JSON parse error: %s", err)
}
equals(t, 5, len(topologies))
equals(t, 6, len(topologies))

found := false
for _, topology := range topologies {
Expand Down
10 changes: 10 additions & 0 deletions probe/docker/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
ImageLabelPrefix = "docker_image_label_"
IsInHostNetwork = "docker_is_in_host_network"
ImageTableID = "image_table"
ServiceName = "service_name"
)

// Exposed for testing
Expand Down Expand Up @@ -132,6 +133,10 @@ var (
Rank: 8,
},
}

SwarmServiceMetadataTemplates = report.MetadataTemplates{
ServiceName: {ID: ServiceName, Label: "Service Name", From: report.FromLatest, Priority: 0},
}
)

// Reporter generate Reports containing Container and ContainerImage topologies
Expand Down Expand Up @@ -177,6 +182,7 @@ func (r *Reporter) Report() (report.Report, error) {
result.Container = result.Container.Merge(r.containerTopology(localAddrs))
result.ContainerImage = result.ContainerImage.Merge(r.containerImageTopology())
result.Overlay = result.Overlay.Merge(r.overlayTopology())
result.SwarmService = result.SwarmService.Merge(r.swarmServiceTopology())
return result, nil
}

Expand Down Expand Up @@ -298,6 +304,10 @@ func (r *Reporter) overlayTopology() report.Topology {
return report.MakeTopology().AddNode(node)
}

func (r *Reporter) swarmServiceTopology() report.Topology {
return report.MakeTopology().WithMetadataTemplates(SwarmServiceMetadataTemplates)
}

// Docker sometimes prefixes ids with a "type" annotation, but it renders a bit
// ugly and isn't necessary, so we should strip it off
func trimImageID(id string) string {
Expand Down
27 changes: 27 additions & 0 deletions probe/docker/tagger.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package docker

import (
"strconv"
"strings"

"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
Expand All @@ -21,6 +22,7 @@ var (

// Tagger is a tagger that tags Docker container information to process
// nodes that have a PID.
// It also populates the SwarmService topology if any of the associated docker labels are present.
type Tagger struct {
registry Registry
procWalker process.Walker
Expand All @@ -44,6 +46,31 @@ func (t *Tagger) Tag(r report.Report) (report.Report, error) {
return report.MakeReport(), err
}
t.tag(tree, &r.Process)

// Scan for Swarm service info
for containerID, container := range r.Container.Nodes {
serviceID, ok := container.Latest.Lookup(LabelPrefix + "com.docker.swarm.service.id")
if !ok {
continue
}
serviceName, ok := container.Latest.Lookup(LabelPrefix + "com.docker.swarm.service.name")
if !ok {
continue
}

if strings.HasPrefix(serviceName, "dockerswarm_") {
serviceName = serviceName[len("dockerswarm_"):]
}

nodeID := report.MakeSwarmServiceNodeID(serviceID)
node := report.MakeNodeWith(nodeID, map[string]string{
ServiceName: serviceName,
})
r.SwarmService = r.SwarmService.AddNode(node)

r.Container.Nodes[containerID] = container.WithParents(container.Parents.Add(report.SwarmService, report.MakeStringSet(nodeID)))
}

return r, nil
}

Expand Down
2 changes: 2 additions & 0 deletions render/detailed/parents.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"sort"

"github.com/weaveworks/scope/probe/awsecs"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/report"
Expand All @@ -27,6 +28,7 @@ var (
report.Service: kubernetesParentLabel,
report.ECSTask: latestLookup(awsecs.TaskFamily),
report.ECSService: ecsServiceParentLabel,
report.SwarmService: latestLookup(docker.ServiceName),
report.ContainerImage: containerImageParentLabel,
report.Host: latestLookup(host.HostName),
}
Expand Down
7 changes: 7 additions & 0 deletions render/detailed/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ var renderers = map[string]func(NodeSummary, report.Node) (NodeSummary, bool){
report.ReplicaSet: podGroupNodeSummary,
report.ECSTask: ecsTaskNodeSummary,
report.ECSService: ecsServiceNodeSummary,
report.SwarmService: swarmServiceNodeSummary,
report.Host: hostNodeSummary,
report.Overlay: weaveNodeSummary,
report.Endpoint: nil, // Do not render
Expand All @@ -93,6 +94,7 @@ var primaryAPITopology = map[string]string{
report.Service: "services",
report.ECSTask: "ecs-tasks",
report.ECSService: "ecs-services",
report.SwarmService: "swarm-services",
report.Host: "hosts",
}

Expand Down Expand Up @@ -276,6 +278,11 @@ func ecsServiceNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool)
return base, true
}

func swarmServiceNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
base.Label, _ = n.Latest.Lookup(docker.ServiceName)
return base, true
}

func hostNodeSummary(base NodeSummary, n report.Node) (NodeSummary, bool) {
var (
hostname, _ = n.Latest.Lookup(host.HostName)
Expand Down
1 change: 1 addition & 0 deletions render/selectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ var (
SelectReplicaSet = TopologySelector(report.ReplicaSet)
SelectECSTask = TopologySelector(report.ECSTask)
SelectECSService = TopologySelector(report.ECSService)
SelectSwarmService = TopologySelector(report.SwarmService)
SelectOverlay = TopologySelector(report.Overlay)
)
26 changes: 26 additions & 0 deletions render/swarm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package render

import (
"github.com/weaveworks/scope/report"
)

// SwarmServiceRenderer is a Renderer for Docker Swarm services
var SwarmServiceRenderer = ConditionalRenderer(renderSwarmTopologies,
MakeMap(
PropagateSingleMetrics(report.Container),
MakeReduce(
MakeMap(
Map2Parent(report.SwarmService, UnmanagedID, nil),
MakeFilter(
IsRunning,
ContainerWithImageNameRenderer,
),
),
SelectSwarmService,
),
),
)

func renderSwarmTopologies(rpt report.Report) bool {
return len(rpt.SwarmService.Nodes) >= 1
}
6 changes: 6 additions & 0 deletions report/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ var (

// ParseECSTaskNodeID parses a replica set node ID
ParseECSTaskNodeID = parseSingleComponentID("ecs_task")

// MakeSwarmServiceNodeID produces a replica set node ID from its composite parts.
MakeSwarmServiceNodeID = makeSingleComponentID("swarm_service")

// ParseSwarmServiceNodeID parses a replica set node ID
ParseSwarmServiceNodeID = parseSingleComponentID("swarm_service")
)

// makeSingleComponentID makes a single-component node id encoder
Expand Down
12 changes: 12 additions & 0 deletions report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
Overlay = "overlay"
ECSService = "ecs_service"
ECSTask = "ecs_task"
SwarmService = "swarm_service"

// Shapes used for different nodes
Circle = "circle"
Expand Down Expand Up @@ -92,6 +93,11 @@ type Report struct {
// Metadata is limited for now, more to come later. Edges are not present.
ECSService Topology

// Swarm Service nodes are Docker Swarm services, which represent a specification for a
// group of tasks (either one per host, or a desired count).
// Edges are not present.
SwarmService Topology

// Overlay nodes are active peers in any software-defined network that's
// overlaid on the infrastructure. The information is scraped by polling
// their status endpoints. Edges could be present, but aren't currently.
Expand Down Expand Up @@ -170,6 +176,10 @@ func MakeReport() Report {
WithShape(Heptagon).
WithLabel("service", "services"),

SwarmService: MakeTopology().
WithShape(Heptagon).
WithLabel("service", "services"),

Sampling: Sampling{},
Window: 0,
Plugins: xfer.MakePluginSpecs(),
Expand All @@ -192,6 +202,7 @@ func (r *Report) TopologyMap() map[string]*Topology {
Overlay: &r.Overlay,
ECSTask: &r.ECSTask,
ECSService: &r.ECSService,
SwarmService: &r.SwarmService,
}
}

Expand Down Expand Up @@ -253,6 +264,7 @@ func (r *Report) WalkPairedTopologies(o *Report, f func(*Topology, *Topology)) {
f(&r.Overlay, &o.Overlay)
f(&r.ECSTask, &o.ECSTask)
f(&r.ECSService, &o.ECSService)
f(&r.SwarmService, &o.SwarmService)
}

// Topology gets a topology by name
Expand Down