Skip to content

Commit

Permalink
Filter by Kubernetes Namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
paulbellamy committed Apr 29, 2016
1 parent a81b116 commit 23fdf47
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 122 deletions.
137 changes: 83 additions & 54 deletions app/api_topologies.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package app

import (
"fmt"
"net/http"
"sort"
"sync"

"github.com/gorilla/mux"
"golang.org/x/net/context"

"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
)
Expand All @@ -21,30 +23,6 @@ var (
)

func init() {
serviceFilters := []APITopologyOptionGroup{
{
ID: "system",
Default: "application",
Options: []APITopologyOption{
{"system", "System services", render.IsSystem},
{"application", "Application services", render.IsApplication},
{"both", "Both", render.Noop},
},
},
}

podFilters := []APITopologyOptionGroup{
{
ID: "system",
Default: "application",
Options: []APITopologyOption{
{"system", "System pods", render.IsSystem},
{"application", "Application pods", render.IsApplication},
{"both", "Both", render.Noop},
},
},
}

containerFilters := []APITopologyOptionGroup{
{
ID: "system",
Expand Down Expand Up @@ -116,31 +94,73 @@ func init() {
Name: "by DNS name",
Options: containerFilters,
},
APITopologyDesc{
id: "hosts",
renderer: render.HostRenderer,
Name: "Hosts",
Rank: 4,
},
APITopologyDesc{
id: "pods",
renderer: render.PodRenderer,
Name: "Pods",
Rank: 3,
HideIfEmpty: true,
Options: podFilters,
},
APITopologyDesc{
id: "pods-by-service",
parent: "pods",
renderer: render.PodServiceRenderer,
Name: "by service",
HideIfEmpty: true,
Options: serviceFilters,
},
APITopologyDesc{
id: "hosts",
renderer: render.HostRenderer,
Name: "Hosts",
Rank: 4,
},
)
}

// kubernetesFilters generates the current kubernetes filters based on the
// available k8s topologies.
func kubernetesFilters(namespaces ...string) APITopologyOptionGroup {
options := APITopologyOptionGroup{ID: "namespace", Default: "all"}
for _, namespace := range namespaces {
options.Options = append(options.Options, APITopologyOption{namespace, namespace, render.IsNamespace(namespace)})
}
options.Options = append(options.Options, APITopologyOption{"all", "All Namespaces", render.Noop})
return options
}

// updateFilters updates the available filters based on the current report.
// Currently only kubernetes changes.
func updateFilters(rpt report.Report, topologies []APITopologyDesc) []APITopologyDesc {
namespaces := map[string]struct{}{}
for _, t := range []report.Topology{rpt.Pod, rpt.Service} {
for _, n := range t.Nodes {
if namespace, ok := n.Latest.Lookup(kubernetes.Namespace); ok {
namespaces[namespace] = struct{}{}
}
}
}
var ns []string
for namespace := range namespaces {
ns = append(ns, namespace)
}
sort.Strings(ns)
for i, t := range topologies {
if t.id == "pods" || t.id == "pods-by-service" {
topologies[i] = updateTopologyFilters(t, []APITopologyOptionGroup{kubernetesFilters(ns...)})
}
}
return topologies
}

// updateTopologyFilters recursively sets the options on a topology description
func updateTopologyFilters(t APITopologyDesc, options []APITopologyOptionGroup) APITopologyDesc {
t.Options = options
for i, sub := range t.SubTopologies {
t.SubTopologies[i] = updateTopologyFilters(sub, options)
}
return t
}

// registry is a threadsafe store of the available topologies
type registry struct {
sync.RWMutex
Expand Down Expand Up @@ -239,23 +259,27 @@ func (r *registry) makeTopologyList(rep Reporter) CtxHandlerFunc {
respondWith(w, http.StatusInternalServerError, err.Error())
return
}
topologies := r.renderTopologies(report, req)
respondWith(w, http.StatusOK, topologies)
respondWith(w, http.StatusOK, r.renderTopologies(report, req))
}
}

func (r *registry) renderTopologies(rpt report.Report, req *http.Request) []APITopologyDesc {
topologies := []APITopologyDesc{}
req.ParseForm()
values := map[string]string{}
for k, vs := range req.Form {
values[k] = vs[0]
}
r.walk(func(desc APITopologyDesc) {
renderer, decorator := renderedForRequest(req, desc)
renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
desc.Stats = decorateWithStats(rpt, renderer, decorator)
for i := range desc.SubTopologies {
renderer, decorator := renderedForRequest(req, desc.SubTopologies[i])
renderer, decorator, _ := r.rendererForTopology(desc.id, values, rpt)
desc.SubTopologies[i].Stats = decorateWithStats(rpt, renderer, decorator)
}
topologies = append(topologies, desc)
})
return topologies
return updateFilters(rpt, topologies)
}

func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator render.Decorator) topologyStats {
Expand All @@ -280,10 +304,16 @@ func decorateWithStats(rpt report.Report, renderer render.Renderer, decorator re
}
}

func renderedForRequest(r *http.Request, topology APITopologyDesc) (render.Renderer, render.Decorator) {
func (r *registry) rendererForTopology(id string, values map[string]string, rpt report.Report) (render.Renderer, render.Decorator, error) {
topology, ok := r.get(id)
if !ok {
return nil, nil, fmt.Errorf("topology not found: %s", id)
}
topology = updateFilters(rpt, []APITopologyDesc{topology})[0]

var filters []render.FilterFunc
for _, group := range topology.Options {
value := r.FormValue(group.ID)
value := values[group.ID]
for _, opt := range group.Options {
if opt.filter == nil {
continue
Expand All @@ -299,23 +329,22 @@ func renderedForRequest(r *http.Request, topology APITopologyDesc) (render.Rende
return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
}
}
return topology.renderer, decorator
return topology.renderer, decorator, nil
}

type reportRenderHandler func(
context.Context,
Reporter, render.Renderer, render.Decorator,
http.ResponseWriter, *http.Request,
)
type reportRenderHandler func(context.Context, Reporter, http.ResponseWriter, *http.Request)

func (r *registry) captureRenderer(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
return func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
topology, ok := r.get(mux.Vars(req)["topology"])
if !ok {
http.NotFound(w, req)
return
}
renderer, decorator := renderedForRequest(req, topology)
f(ctx, rep, renderer, decorator, w, req)
func (r *registry) rendererForRequest(req *http.Request, rpt report.Report) (render.Renderer, render.Decorator, error) {
req.ParseForm()
values := map[string]string{}
for k, vs := range req.Form {
values[k] = vs[0]
}
return r.rendererForTopology(mux.Vars(req)["topology"], values, rpt)
}

func captureReporter(rep Reporter, f reportRenderHandler) CtxHandlerFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
f(ctx, rep, w, r)
}
}
50 changes: 29 additions & 21 deletions app/api_topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"golang.org/x/net/context"

"github.com/weaveworks/scope/common/xfer"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/render/detailed"
)

Expand All @@ -28,27 +27,24 @@ type APINode struct {
}

// Full topology.
func handleTopology(
ctx context.Context,
rep Reporter, renderer render.Renderer, decorator render.Decorator,
w http.ResponseWriter, r *http.Request,
) {
func handleTopology(ctx context.Context, rep Reporter, w http.ResponseWriter, r *http.Request) {
report, err := rep.Report(ctx)
if err != nil {
respondWith(w, http.StatusInternalServerError, err.Error())
return
}
renderer, decorator, err := topologyRegistry.rendererForRequest(r, report)
if err != nil {
http.NotFound(w, r)
return
}
respondWith(w, http.StatusOK, APITopology{
Nodes: detailed.Summaries(report, renderer.Render(report, decorator)),
})
}

// Websocket for the full topology. This route overlaps with the next.
func handleWs(
ctx context.Context,
rep Reporter, renderer render.Renderer, decorator render.Decorator,
w http.ResponseWriter, r *http.Request,
) {
func handleWs(ctx context.Context, rep Reporter, w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
respondWith(w, http.StatusInternalServerError, err.Error())
return
Expand All @@ -61,22 +57,31 @@ func handleWs(
return
}
}
handleWebsocket(ctx, w, r, rep, renderer, decorator, loop)
handleWebsocket(ctx, w, r, rep, loop)
}

// Individual nodes.
func handleNode(
ctx context.Context,
rep Reporter, renderer render.Renderer, _ render.Decorator,
w http.ResponseWriter, r *http.Request,
) {
func handleNode(ctx context.Context, rep Reporter, w http.ResponseWriter, r *http.Request) {
var (
vars = mux.Vars(r)
topologyID = vars["topology"]
nodeID = vars["id"]
report, err = rep.Report(ctx)
rendered = renderer.Render(report, render.FilterNoop)
node, ok = rendered[nodeID]
)
if err != nil {
respondWith(w, http.StatusInternalServerError, err.Error())
return
}

renderer, _, err := topologyRegistry.rendererForRequest(r, report)
if err != nil {
http.NotFound(w, r)
return
}

var (
rendered = renderer.Render(report, nil)
node, ok = rendered[nodeID]
)
if err != nil {
respondWith(w, http.StatusInternalServerError, err.Error())
Expand All @@ -94,8 +99,6 @@ func handleWebsocket(
w http.ResponseWriter,
r *http.Request,
rep Reporter,
renderer render.Renderer,
decorator render.Decorator,
loop time.Duration,
) {
conn, err := xfer.Upgrade(w, r, nil)
Expand Down Expand Up @@ -132,6 +135,11 @@ func handleWebsocket(
log.Errorf("Error generating report: %v", err)
return
}
renderer, decorator, err := topologyRegistry.rendererForRequest(r, report)
if err != nil {
log.Errorf("Error generating report: %v", err)
return
}
newTopo := detailed.Summaries(report, renderer.Render(report, decorator))
diff := detailed.TopoDiff(previousTopo, newTopo)
previousTopo = newTopo
Expand Down
6 changes: 3 additions & 3 deletions app/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ func RegisterTopologyRoutes(router *mux.Router, r Reporter) {
get.HandleFunc("/api/topology",
gzipHandler(requestContextDecorator(topologyRegistry.makeTopologyList(r))))
get.HandleFunc("/api/topology/{topology}",
gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleTopology))))
gzipHandler(requestContextDecorator(captureReporter(r, handleTopology))))
get.HandleFunc("/api/topology/{topology}/ws",
requestContextDecorator(topologyRegistry.captureRenderer(r, handleWs))) // NB not gzip!
requestContextDecorator(captureReporter(r, handleWs))) // NB not gzip!
get.MatcherFunc(URLMatcher("/api/topology/{topology}/{id}")).HandlerFunc(
gzipHandler(requestContextDecorator(topologyRegistry.captureRenderer(r, handleNode))))
gzipHandler(requestContextDecorator(captureReporter(r, handleNode))))
get.HandleFunc("/api/report",
gzipHandler(requestContextDecorator(makeRawReportHandler(r))))
get.HandleFunc("/api/probes",
Expand Down
Loading

0 comments on commit 23fdf47

Please sign in to comment.