Merge pull request #2444 from weaveworks/mike/docker-swarm/services-topology

Add very basic Docker Swarm service topology
This commit is contained in:
Mike Lang
2017-04-14 17:55:16 -07:00
committed by GitHub
10 changed files with 104 additions and 4 deletions

View File

@@ -33,6 +33,7 @@ const (
weaveID = "weave"
ecsTasksID = "ecs-tasks"
ecsServicesID = "ecs-services"
swarmServicesID = "swarm-services"
)
var (
@@ -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,

View File

@@ -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)
@@ -50,7 +50,7 @@ func TestAPITopology(t *testing.T) {
}
// TODO: add ECS nodes in report fixture
if topology.Name == "Tasks" {
if topology.Name == "Tasks" || topology.Name == "services" {
continue
}
@@ -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()
@@ -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 {

View File

@@ -21,6 +21,7 @@ const (
ImageLabelPrefix = "docker_image_label_"
IsInHostNetwork = "docker_is_in_host_network"
ImageTableID = "image_table"
ServiceName = "service_name"
)
// Exposed for testing
@@ -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
@@ -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
}
@@ -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 {

View File

@@ -2,6 +2,7 @@ package docker
import (
"strconv"
"strings"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
@@ -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
@@ -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
}

View File

@@ -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"
@@ -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),
}

View File

@@ -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
@@ -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",
}
@@ -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)

View File

@@ -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
render/swarm.go Normal file
View File

@@ -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
}

View File

@@ -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

View File

@@ -24,6 +24,7 @@ const (
Overlay = "overlay"
ECSService = "ecs_service"
ECSTask = "ecs_task"
SwarmService = "swarm_service"
// Shapes used for different nodes
Circle = "circle"
@@ -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.
@@ -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(),
@@ -192,6 +202,7 @@ func (r *Report) TopologyMap() map[string]*Topology {
Overlay: &r.Overlay,
ECSTask: &r.ECSTask,
ECSService: &r.ECSService,
SwarmService: &r.SwarmService,
}
}
@@ -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