From 51999529a71312e5f3154458ce3bfebf5ee6b81b Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Tue, 18 Apr 2017 09:08:22 -0700 Subject: [PATCH] Add docker swarm Stack selector ala k8s namespace selector We have to introduce the kinda hacky concept of a 'No Stack' stack to reconcile it with the idea of a 'default' k8s namespace. This is important because swarm services without a stack don't have the same docker labels as ones that do. Curiously, they still have what appears to be a stack name 'prefix' on their names, but I can't isolate that name anywhere easily so they'll just have to make do. I basically copy-pasted updateFilters to make this work, todo go back and refactor to not duplicate 90% of the code. --- app/api_topologies.go | 47 ++++++++++++++++++++++++++++++++++------ probe/docker/reporter.go | 1 + probe/docker/tagger.go | 12 +++++----- render/filters.go | 9 ++++++-- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/app/api_topologies.go b/app/api_topologies.go index fd68a2ac9..387857ab7 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -12,6 +12,7 @@ import ( "github.com/gorilla/mux" "golang.org/x/net/context" + "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/probe/kubernetes" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" @@ -48,12 +49,12 @@ var ( } ) -// kubernetesFilters generates the current kubernetes filters based on the -// available k8s topologies. -func kubernetesFilters(namespaces ...string) APITopologyOptionGroup { - options := APITopologyOptionGroup{ID: "namespace", Default: "", SelectType: "union", NoneLabel: "All Namespaces"} +// namespaceFilters generates a namespace selector option group based on the given namespaces +func namespaceFilters(namespaces []string, defaultNamespace, noneLabel string) APITopologyOptionGroup { + options := APITopologyOptionGroup{ID: "namespace", Default: "", SelectType: "union", NoneLabel: noneLabel} for _, namespace := range namespaces { - if namespace == "default" { + if defaultNamespace != "" && namespace == defaultNamespace { + // We only set the default namespace as options.Default if it is present, otherwise default to All options.Default = namespace } options.Options = append(options.Options, APITopologyOption{ @@ -64,8 +65,40 @@ func kubernetesFilters(namespaces ...string) APITopologyOptionGroup { } // updateFilters updates the available filters based on the current report. -// Currently only kubernetes changes. func updateFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc { + topologies = updateKubeFilters(rpt, topologies) + topologies = updateSwarmFilters(rpt, topologies) + return topologies +} + +func updateSwarmFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc { + namespaces := map[string]struct{}{} + for _, n := range rpt.SwarmService.Nodes { + if namespace, ok := n.Latest.Lookup(docker.StackNamespace); ok { + namespaces[namespace] = struct{}{} + } + } + if len(namespaces) == 0 { + // We only want to apply filters when we have swarm-related nodes, + // so if we don't then return early + return topologies + } + ns := []string{} + for namespace := range namespaces { + ns = append(ns, namespace) + } + topologies = append([]APITopologyDesc{}, topologies...) // Make a copy so we can make changes safely + for i, t := range topologies { + if t.id == containersID || t.id == containersByImageID || t.id == containersByHostnameID || t.id == swarmServicesID { + topologies[i] = mergeTopologyFilters(t, []APITopologyOptionGroup{ + namespaceFilters(ns, docker.DefaultNamespace, "All Stacks"), + }) + } + } + return topologies +} + +func updateKubeFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc { namespaces := map[string]struct{}{} for _, t := range []report.Topology{rpt.Pod, rpt.Service, rpt.Deployment, rpt.ReplicaSet} { for _, n := range t.Nodes { @@ -91,7 +124,7 @@ func updateFilters(rpt report.Report, topologies []APITopologyDesc) []APITopolog for i, t := range topologies { if t.id == containersID || t.id == containersByImageID || t.id == containersByHostnameID || t.id == podsID || t.id == servicesID || t.id == deploymentsID || t.id == replicaSetsID { topologies[i] = mergeTopologyFilters(t, []APITopologyOptionGroup{ - kubernetesFilters(ns...), + namespaceFilters(ns, "default", "All Namespaces"), }) } } diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index b925b12c8..f714b2837 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -23,6 +23,7 @@ const ( ImageTableID = "image_table" ServiceName = "service_name" StackNamespace = "stack_namespace" + DefaultNamespace = "No Stack" ) // Exposed for testing diff --git a/probe/docker/tagger.go b/probe/docker/tagger.go index 8d3d07214..3bdf04f55 100644 --- a/probe/docker/tagger.go +++ b/probe/docker/tagger.go @@ -59,12 +59,12 @@ func (t *Tagger) Tag(r report.Report) (report.Report, error) { } stackNamespace, ok := container.Latest.Lookup(LabelPrefix + "com.docker.stack.namespace") if !ok { - continue - } - - prefix := stackNamespace + "_" - if strings.HasPrefix(serviceName, prefix) { - serviceName = serviceName[len(prefix):] + stackNamespace = DefaultNamespace + } else { + prefix := stackNamespace + "_" + if strings.HasPrefix(serviceName, prefix) { + serviceName = serviceName[len(prefix):] + } } nodeID := report.MakeSwarmServiceNodeID(serviceID) diff --git a/render/filters.go b/render/filters.go index f7df8d337..93721d36a 100644 --- a/render/filters.go +++ b/render/filters.go @@ -11,7 +11,8 @@ import ( ) const ( - k8sNamespaceLabel = "io.kubernetes.pod.namespace" + k8sNamespaceLabel = "io.kubernetes.pod.namespace" + swarmNamespaceLabel = "com.docker.stack.namespace" ) // PreciousNodeRenderer ensures a node is never filtered out by decorators @@ -330,7 +331,7 @@ func IsPseudoTopology(n report.Node) bool { // IsNamespace checks if the node is a pod/service in the specified namespace func IsNamespace(namespace string) FilterFunc { return func(n report.Node) bool { - tryKeys := []string{kubernetes.Namespace, docker.LabelPrefix + k8sNamespaceLabel} + tryKeys := []string{kubernetes.Namespace, docker.LabelPrefix + k8sNamespaceLabel, docker.StackNamespace, docker.LabelPrefix + swarmNamespaceLabel} gotNamespace := "" for _, key := range tryKeys { if value, ok := n.Latest.Lookup(key); ok { @@ -338,6 +339,10 @@ func IsNamespace(namespace string) FilterFunc { break } } + // Special case for docker + if namespace == docker.DefaultNamespace && gotNamespace == "" { + return true + } return namespace == gotNamespace } }