From 23bdae305c7bc8928314b84b38c83d7af30fce87 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 15 Jun 2015 13:13:01 +0000 Subject: [PATCH] Introduce renderers; allow them to recurse. --- app/api_topologies.go | 4 ++-- app/api_topology.go | 21 ++++------------- app/router.go | 53 ++++++++++++++++--------------------------- circle.yml | 1 + render/render.go | 51 +++++++++++++++++++++++++++++++++++++++++ render/render_test.go | 47 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 52 deletions(-) create mode 100644 render/render.go create mode 100644 render/render_test.go diff --git a/app/api_topologies.go b/app/api_topologies.go index f7ac4157b0..e5f3a07bda 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -37,7 +37,7 @@ func makeTopologyList(rep Reporter) func(w http.ResponseWriter, r *http.Request) subTopologies = append(subTopologies, APITopologyDesc{ Name: subDef.human, URL: "/api/topology/" + subName, - Stats: stats(render(rpt, subDef.maps)), + Stats: stats(subDef.renderer.Render(rpt)), }) } } @@ -45,7 +45,7 @@ func makeTopologyList(rep Reporter) func(w http.ResponseWriter, r *http.Request) Name: def.human, URL: "/api/topology/" + name, SubTopologies: subTopologies, - Stats: stats(render(rpt, def.maps)), + Stats: stats(def.renderer.Render(rpt)), }) } respondWith(w, http.StatusOK, topologies) diff --git a/app/api_topology.go b/app/api_topology.go index 801209f80d..b416ee997b 100644 --- a/app/api_topology.go +++ b/app/api_topology.go @@ -30,19 +30,10 @@ type APIEdge struct { Metadata report.AggregateMetadata `json:"metadata"` } -func render(rpt report.Report, maps []topologyMapper) report.RenderableNodes { - result := report.RenderableNodes{} - for _, m := range maps { - rns := m.selector(rpt).RenderBy(m.mapper, m.pseudo) - result.Merge(rns) - } - return result -} - // Full topology. func handleTopology(rep Reporter, t topologyView, w http.ResponseWriter, r *http.Request) { respondWith(w, http.StatusOK, APITopology{ - Nodes: render(rep.Report(), t.maps), + Nodes: t.renderer.Render(rep.Report()), }) } @@ -69,7 +60,7 @@ func handleNode(rep Reporter, t topologyView, w http.ResponseWriter, r *http.Req vars = mux.Vars(r) nodeID = vars["id"] rpt = rep.Report() - node, ok = render(rpt, t.maps)[nodeID] + node, ok = t.renderer.Render(rep.Report())[nodeID] ) if !ok { http.NotFound(w, r) @@ -85,13 +76,9 @@ func handleEdge(rep Reporter, t topologyView, w http.ResponseWriter, r *http.Req localID = vars["local"] remoteID = vars["remote"] rpt = rep.Report() - metadata = report.AggregateMetadata{} + metadata = t.renderer.EdgeMetadata(rpt, localID, remoteID) ) - for _, m := range t.maps { - metadata.Merge(m.selector(rpt).EdgeMetadata(m.mapper, localID, remoteID).Transform()) - } - respondWith(w, http.StatusOK, APIEdge{Metadata: metadata}) } @@ -128,7 +115,7 @@ func handleWebsocket( tick = time.Tick(loop) ) for { - newTopo := render(rep.Report(), t.maps) + newTopo := t.renderer.Render(rep.Report()) diff := report.TopoDiff(previousTopo, newTopo) previousTopo = newTopo diff --git a/app/router.go b/app/router.go index 4616eb8bcb..04f2bc48d6 100644 --- a/app/router.go +++ b/app/router.go @@ -5,6 +5,7 @@ import ( "github.com/gorilla/mux" + "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/report" ) @@ -47,51 +48,37 @@ func apiHandler(w http.ResponseWriter, r *http.Request) { var topologyRegistry = map[string]topologyView{ "applications": { - human: "Applications", - parent: "", - maps: []topologyMapper{ - {report.SelectEndpoint, report.ProcessPID, report.GenericPseudoNode}, - }, + human: "Applications", + parent: "", + renderer: render.Map{Selector: report.SelectEndpoint, Mapper: report.ProcessPID, Pseudo: report.GenericPseudoNode}, }, "applications-by-name": { - human: "by name", - parent: "applications", - maps: []topologyMapper{ - {report.SelectEndpoint, report.ProcessName, report.GenericGroupedPseudoNode}, - }, + human: "by name", + parent: "applications", + renderer: render.Map{Selector: report.SelectEndpoint, Mapper: report.ProcessName, Pseudo: report.GenericGroupedPseudoNode}, }, "containers": { human: "Containers", parent: "", - maps: []topologyMapper{ - {report.SelectEndpoint, report.MapEndpoint2Container, report.InternetOnlyPseudoNode}, - {report.SelectContainer, report.MapContainerIdentity, report.InternetOnlyPseudoNode}, - }, + renderer: render.Reduce([]render.Renderer{ + render.Map{Selector: report.SelectEndpoint, Mapper: report.MapEndpoint2Container, Pseudo: report.InternetOnlyPseudoNode}, + render.Map{Selector: report.SelectContainer, Mapper: report.MapContainerIdentity, Pseudo: report.InternetOnlyPseudoNode}, + }), }, "containers-by-image": { - human: "by image", - parent: "containers", - maps: []topologyMapper{ - {report.SelectEndpoint, report.ProcessContainerImage, report.InternetOnlyPseudoNode}, - }, + human: "by image", + parent: "containers", + renderer: render.Map{Selector: report.SelectEndpoint, Mapper: report.ProcessContainerImage, Pseudo: report.InternetOnlyPseudoNode}, }, "hosts": { - human: "Hosts", - parent: "", - maps: []topologyMapper{ - {report.SelectAddress, report.NetworkHostname, report.GenericPseudoNode}, - }, + human: "Hosts", + parent: "", + renderer: render.Map{Selector: report.SelectAddress, Mapper: report.NetworkHostname, Pseudo: report.GenericPseudoNode}, }, } type topologyView struct { - human string - parent string - maps []topologyMapper -} - -type topologyMapper struct { - selector report.TopologySelector - mapper report.MapFunc - pseudo report.PseudoFunc + human string + parent string + renderer render.Renderer } diff --git a/circle.yml b/circle.yml index 4e74f2b763..3012cedd76 100644 --- a/circle.yml +++ b/circle.yml @@ -25,6 +25,7 @@ dependencies: mv scope_ui_build.tar $(dirname "$SCOPE_UI_BUILD"); fi post: + - go version - go clean -i net - go install -tags netgo std - make deps diff --git a/render/render.go b/render/render.go new file mode 100644 index 0000000000..87943fa0d0 --- /dev/null +++ b/render/render.go @@ -0,0 +1,51 @@ +package render + +import ( + "github.com/weaveworks/scope/report" +) + +// Renderer is something that can render a report to a set of RenderableNodes +type Renderer interface { + Render(report.Report) report.RenderableNodes + EdgeMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata +} + +// Reduce renderer is a Renderer which merges together the output of several +// other renderers +type Reduce []Renderer + +// Render produces a set of RenderableNodes given a Report +func (r Reduce) Render(rpt report.Report) report.RenderableNodes { + result := report.RenderableNodes{} + for _, renderer := range r { + result.Merge(renderer.Render(rpt)) + } + return result +} + +// EdgeMetadata produces an AggregateMetadata for a given edge +func (r Reduce) EdgeMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata { + metadata := report.AggregateMetadata{} + for _, renderer := range r { + metadata.Merge(renderer.EdgeMetadata(rpt, localID, remoteID)) + } + return metadata +} + +// Map is a Renderer which produces a set of RendererNodes by using a +// Mapper functions and topology selector. +type Map struct { + Selector report.TopologySelector + Mapper report.MapFunc + Pseudo report.PseudoFunc +} + +// Render produces a set of RenderableNodes given a Report +func (m Map) Render(rpt report.Report) report.RenderableNodes { + return m.Selector(rpt).RenderBy(m.Mapper, m.Pseudo) +} + +// EdgeMetadata produces an AggregateMetadata for a given edge +func (m Map) EdgeMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata { + return m.Selector(rpt).EdgeMetadata(m.Mapper, localID, remoteID).Transform() +} diff --git a/render/render_test.go b/render/render_test.go new file mode 100644 index 0000000000..75b808654a --- /dev/null +++ b/render/render_test.go @@ -0,0 +1,47 @@ +package render_test + +import ( + "reflect" + "testing" + + "github.com/weaveworks/scope/render" + "github.com/weaveworks/scope/report" +) + +type mockRenderer struct { + nodes report.RenderableNodes + amd report.AggregateMetadata +} + +func (m mockRenderer) Render(rpt report.Report) report.RenderableNodes { return m.nodes } +func (m mockRenderer) EdgeMetadata(rpt report.Report, localID, remoteID string) report.AggregateMetadata { + return m.amd +} + +func TestReduceRender(t *testing.T) { + renderer := render.Reduce{ + mockRenderer{nodes: report.RenderableNodes{"foo": {ID: "foo"}}}, + mockRenderer{nodes: report.RenderableNodes{"bar": {ID: "bar"}}}, + } + + want := report.RenderableNodes{"foo": {ID: "foo"}, "bar": {ID: "bar"}} + have := renderer.Render(report.MakeReport()) + + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) + } +} + +func TestReduceEdge(t *testing.T) { + renderer := render.Reduce{ + mockRenderer{amd: report.AggregateMetadata{"foo": 1}}, + mockRenderer{amd: report.AggregateMetadata{"bar": 2}}, + } + + want := report.AggregateMetadata{"foo": 1, "bar": 2} + have := renderer.EdgeMetadata(report.MakeReport(), "", "") + + if !reflect.DeepEqual(want, have) { + t.Errorf("want %+v, have %+v", want, have) + } +}