diff --git a/.gitignore b/.gitignore index 47068f7376..c3f4fbd0d7 100644 --- a/.gitignore +++ b/.gitignore @@ -34,8 +34,8 @@ coverage.html .*.uptodate scope.tar scope_ui_build.tar -app/app -app/scope-app +prog/app/app +prog/app/scope-app prog/probe/probe prog/probe/scope-probe docker/scope-app @@ -55,3 +55,4 @@ experimental/_integration/_integration *sublime-workspace *npm-debug.log app/static.go +prog/app/static.go diff --git a/Makefile b/Makefile index 81cdb9c2cd..3bc6d1f4d2 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # If you can use Docker without being root, you can `make SUDO= ` SUDO=sudo -E DOCKERHUB_USER=weaveworks -APP_EXE=app/scope-app +APP_EXE=prog/app/scope-app PROBE_EXE=prog/probe/scope-probe FIXPROBE_EXE=experimental/fixprobe/fixprobe SCOPE_IMAGE=$(DOCKERHUB_USER)/scope @@ -38,7 +38,7 @@ $(SCOPE_EXPORT): $(APP_EXE) $(PROBE_EXE) $(DOCKER_DISTRIB) docker/weave $(RUNSVI $(RUNSVINIT): vendor/runsvinit/*.go -$(APP_EXE): app/*.go render/*.go report/*.go xfer/*.go common/sanitize/*.go app/static.go +$(APP_EXE): app/*.go render/*.go report/*.go xfer/*.go common/sanitize/*.go prog/app/*.go prog/app/static.go $(PROBE_EXE): prog/probe/*.go $(shell find probe/ -type f -name *.go) report/*.go xfer/*.go common/sanitize/*.go common/exec/*.go @@ -62,9 +62,9 @@ $(RUNSVINIT): go build -ldflags "-extldflags \"-static\"" -o $@ ./$(@D) endif -static: app/static.go +static: prog/app/static.go -app/static.go: client/build/app.js +prog/app/static.go: client/build/app.js esc -o $@ -prefix client/build client/build ifeq ($(BUILD_IN_CONTAINER),true) diff --git a/app/api_report.go b/app/api_report.go index f4bfa329c9..f96cdc0308 100644 --- a/app/api_report.go +++ b/app/api_report.go @@ -1,4 +1,4 @@ -package main +package app import ( "net/http" diff --git a/app/api_report_test.go b/app/api_report_test.go index 737a9173c2..d355571611 100644 --- a/app/api_report_test.go +++ b/app/api_report_test.go @@ -1,15 +1,24 @@ -package main +package app_test import ( "encoding/json" "net/http/httptest" "testing" + "github.com/gorilla/mux" + + "github.com/weaveworks/scope/app" "github.com/weaveworks/scope/report" ) +func topologyServer() *httptest.Server { + router := mux.NewRouter() + app.RegisterTopologyRoutes(StaticReport{}, router) + return httptest.NewServer(router) +} + func TestAPIReport(t *testing.T) { - ts := httptest.NewServer(Router(StaticReport{})) + ts := topologyServer() defer ts.Close() is404(t, ts, "/api/report/foobar") diff --git a/app/api_topologies.go b/app/api_topologies.go index 7e6b026cd1..d9c1862923 100644 --- a/app/api_topologies.go +++ b/app/api_topologies.go @@ -1,4 +1,4 @@ -package main +package app import ( "net/http" diff --git a/app/api_topologies_test.go b/app/api_topologies_test.go index c75206d918..03aa5b1afb 100644 --- a/app/api_topologies_test.go +++ b/app/api_topologies_test.go @@ -1,4 +1,4 @@ -package main +package app_test import ( "bytes" @@ -6,21 +6,24 @@ import ( "encoding/json" "net/http/httptest" "testing" + "time" + "github.com/gorilla/mux" "k8s.io/kubernetes/pkg/api" + "github.com/weaveworks/scope/app" "github.com/weaveworks/scope/probe/kubernetes" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test/fixture" ) func TestAPITopology(t *testing.T) { - ts := httptest.NewServer(Router(StaticReport{})) + ts := topologyServer() defer ts.Close() body := getRawJSON(t, ts, "/api/topology") - var topologies []APITopologyDesc + var topologies []app.APITopologyDesc if err := json.Unmarshal(body, &topologies); err != nil { t.Fatalf("JSON parse error: %s", err) } @@ -48,12 +51,16 @@ func TestAPITopology(t *testing.T) { } func TestAPITopologyAddsKubernetes(t *testing.T) { - ts := httptest.NewServer(Router(StaticReport{})) + router := mux.NewRouter() + c := app.NewCollector(1 * time.Minute) + app.RegisterTopologyRoutes(c, router) + app.RegisterReportPostHandler(c, router) + ts := httptest.NewServer(router) defer ts.Close() body := getRawJSON(t, ts, "/api/topology") - var topologies []APITopologyDesc + var topologies []app.APITopologyDesc if err := json.Unmarshal(body, &topologies); err != nil { t.Fatalf("JSON parse error: %s", err) } diff --git a/app/api_topology.go b/app/api_topology.go index 00d86c85ab..d22a0a5d0a 100644 --- a/app/api_topology.go +++ b/app/api_topology.go @@ -1,4 +1,4 @@ -package main +package app import ( "net/http" diff --git a/app/api_topology_test.go b/app/api_topology_test.go index c4db880d84..eaf11aef6f 100644 --- a/app/api_topology_test.go +++ b/app/api_topology_test.go @@ -1,15 +1,15 @@ -package main +package app_test import ( "encoding/json" "fmt" - "net/http/httptest" "net/url" "reflect" "testing" "github.com/gorilla/websocket" + "github.com/weaveworks/scope/app" "github.com/weaveworks/scope/render" "github.com/weaveworks/scope/render/expected" "github.com/weaveworks/scope/report" @@ -18,25 +18,25 @@ import ( ) func TestAll(t *testing.T) { - ts := httptest.NewServer(Router(StaticReport{})) + ts := topologyServer() defer ts.Close() body := getRawJSON(t, ts, "/api/topology") - var topologies []APITopologyDesc + var topologies []app.APITopologyDesc if err := json.Unmarshal(body, &topologies); err != nil { t.Fatalf("JSON parse error: %s", err) } getTopology := func(topologyURL string) { body := getRawJSON(t, ts, topologyURL) - var topology APITopology + var topology app.APITopology if err := json.Unmarshal(body, &topology); err != nil { t.Fatalf("JSON parse error: %s", err) } for _, node := range topology.Nodes { body := getRawJSON(t, ts, fmt.Sprintf("%s/%s", topologyURL, url.QueryEscape(node.ID))) - var node APINode + var node app.APINode if err := json.Unmarshal(body, &node); err != nil { t.Fatalf("JSON parse error: %s", err) } @@ -53,10 +53,10 @@ func TestAll(t *testing.T) { } func TestAPITopologyContainers(t *testing.T) { - ts := httptest.NewServer(Router(StaticReport{})) + ts := topologyServer() { body := getRawJSON(t, ts, "/api/topology/containers") - var topo APITopology + var topo app.APITopology if err := json.Unmarshal(body, &topo); err != nil { t.Fatal(err) } @@ -73,12 +73,12 @@ func TestAPITopologyContainers(t *testing.T) { } func TestAPITopologyApplications(t *testing.T) { - ts := httptest.NewServer(Router(StaticReport{})) + ts := topologyServer() defer ts.Close() is404(t, ts, "/api/topology/applications/foobar") { body := getRawJSON(t, ts, "/api/topology/applications/"+expected.ServerProcessID) - var node APINode + var node app.APINode if err := json.Unmarshal(body, &node); err != nil { t.Fatal(err) } @@ -90,7 +90,7 @@ func TestAPITopologyApplications(t *testing.T) { } { body := getRawJSON(t, ts, fmt.Sprintf("/api/topology/applications/%s/%s", expected.ClientProcess1ID, expected.ServerProcessID)) - var edge APIEdge + var edge app.APIEdge if err := json.Unmarshal(body, &edge); err != nil { t.Fatalf("JSON parse error: %s", err) } @@ -104,12 +104,12 @@ func TestAPITopologyApplications(t *testing.T) { } func TestAPITopologyHosts(t *testing.T) { - ts := httptest.NewServer(Router(StaticReport{})) + ts := topologyServer() defer ts.Close() is404(t, ts, "/api/topology/hosts/foobar") { body := getRawJSON(t, ts, "/api/topology/hosts") - var topo APITopology + var topo app.APITopology if err := json.Unmarshal(body, &topo); err != nil { t.Fatal(err) } @@ -120,7 +120,7 @@ func TestAPITopologyHosts(t *testing.T) { } { body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostRenderedID) - var node APINode + var node app.APINode if err := json.Unmarshal(body, &node); err != nil { t.Fatal(err) } @@ -132,7 +132,7 @@ func TestAPITopologyHosts(t *testing.T) { } { body := getRawJSON(t, ts, fmt.Sprintf("/api/topology/hosts/%s/%s", expected.ClientHostRenderedID, expected.ServerHostRenderedID)) - var edge APIEdge + var edge app.APIEdge if err := json.Unmarshal(body, &edge); err != nil { t.Fatalf("JSON parse error: %s", err) } @@ -146,7 +146,7 @@ func TestAPITopologyHosts(t *testing.T) { // Basic websocket test func TestAPITopologyWebsocket(t *testing.T) { - ts := httptest.NewServer(Router(StaticReport{})) + ts := topologyServer() defer ts.Close() url := "/api/topology/applications/ws" diff --git a/app/collector.go b/app/collector.go index 394043c523..aebcb08e03 100644 --- a/app/collector.go +++ b/app/collector.go @@ -1,4 +1,4 @@ -package main +package app import ( "sync" @@ -21,9 +21,15 @@ type Adder interface { Add(report.Report) } +// A Collector is a Reporter and an Adder +type Collector interface { + Reporter + Adder +} + // Collector receives published reports from multiple producers. It yields a // single merged report, representing all collected reports. -type Collector struct { +type collector struct { mtx sync.Mutex reports []timestampReport window time.Duration @@ -60,8 +66,8 @@ func (wc *waitableCondition) Broadcast() { } // NewCollector returns a collector ready for use. -func NewCollector(window time.Duration) *Collector { - return &Collector{ +func NewCollector(window time.Duration) Collector { + return &collector{ window: window, waitableCondition: waitableCondition{ waiters: map[chan struct{}]struct{}{}, @@ -72,7 +78,7 @@ func NewCollector(window time.Duration) *Collector { var now = time.Now // Add adds a report to the collector's internal state. It implements Adder. -func (c *Collector) Add(rpt report.Report) { +func (c *collector) Add(rpt report.Report) { c.mtx.Lock() defer c.mtx.Unlock() c.reports = append(c.reports, timestampReport{now(), rpt}) @@ -84,7 +90,7 @@ func (c *Collector) Add(rpt report.Report) { // Report returns a merged report over all added reports. It implements // Reporter. -func (c *Collector) Report() report.Report { +func (c *collector) Report() report.Report { c.mtx.Lock() defer c.mtx.Unlock() diff --git a/app/collector_test.go b/app/collector_test.go index a350ee4948..00a18077e6 100644 --- a/app/collector_test.go +++ b/app/collector_test.go @@ -1,17 +1,18 @@ -package main +package app_test import ( "reflect" "testing" "time" + "github.com/weaveworks/scope/app" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" ) func TestCollector(t *testing.T) { window := time.Millisecond - c := NewCollector(window) + c := app.NewCollector(window) r1 := report.MakeReport() r1.Endpoint.AddNode("foo", report.MakeNode()) diff --git a/app/controls.go b/app/controls.go index 4e59d9c4b4..f2f5f94fc6 100644 --- a/app/controls.go +++ b/app/controls.go @@ -1,4 +1,4 @@ -package main +package app import ( "log" @@ -12,7 +12,8 @@ import ( "github.com/weaveworks/scope/xfer" ) -func registerControlRoutes(router *mux.Router) { +// RegisterControlRoutes registers the various control routes with a http mux. +func RegisterControlRoutes(router *mux.Router) { controlRouter := &controlRouter{ probes: map[string]controlHandler{}, } diff --git a/app/controls_test.go b/app/controls_test.go index d5f3a173dc..2586770923 100644 --- a/app/controls_test.go +++ b/app/controls_test.go @@ -1,4 +1,4 @@ -package main +package app_test import ( "encoding/json" @@ -9,14 +9,15 @@ import ( "testing" "time" - "github.com/weaveworks/scope/xfer" - "github.com/gorilla/mux" + + "github.com/weaveworks/scope/app" + "github.com/weaveworks/scope/xfer" ) func TestControl(t *testing.T) { router := mux.NewRouter() - registerControlRoutes(router) + app.RegisterControlRoutes(router) server := httptest.NewServer(router) defer server.Close() diff --git a/app/mock_reporter_test.go b/app/mock_reporter_test.go index 9e27941c19..625791dec7 100644 --- a/app/mock_reporter_test.go +++ b/app/mock_reporter_test.go @@ -1,4 +1,4 @@ -package main +package app_test import ( "github.com/weaveworks/scope/report" diff --git a/app/router.go b/app/router.go index 3d142f588c..d33dbc23eb 100644 --- a/app/router.go +++ b/app/router.go @@ -1,4 +1,4 @@ -package main +package app import ( "compress/gzip" @@ -15,6 +15,14 @@ import ( "github.com/weaveworks/scope/xfer" ) +var ( + // Version - set at buildtime. + Version = "dev" + + // UniqueID - set at runtime. + UniqueID = "0" +) + // URLMatcher uses request.RequestURI (the raw, unparsed request) to attempt // to match pattern. It does this as go's URL.Parse method is broken, and // mistakenly unescapes the Path before parsing it. This breaks %2F (encoded @@ -46,16 +54,12 @@ func URLMatcher(pattern string) mux.MatcherFunc { } } -type collector interface { - Reporter - Adder -} - func gzipHandler(h http.HandlerFunc) http.HandlerFunc { return handlers.GZIPHandlerFunc(h, nil) } -func registerTopologyRoutes(c collector, router *mux.Router) { +// RegisterTopologyRoutes registers the various topology routes with a http mux. +func RegisterTopologyRoutes(c Reporter, router *mux.Router) { get := router.Methods("GET").Subrouter() get.HandleFunc("/api", gzipHandler(apiHandler)) get.HandleFunc("/api/topology", gzipHandler(topologyRegistry.makeTopologyList(c))) @@ -68,13 +72,12 @@ func registerTopologyRoutes(c collector, router *mux.Router) { get.MatcherFunc(URLMatcher("/api/topology/{topology}/{local}/{remote}")).HandlerFunc( gzipHandler(topologyRegistry.captureRenderer(c, handleEdge))) get.HandleFunc("/api/report", gzipHandler(makeRawReportHandler(c))) - - post := router.Methods("POST").Subrouter() - post.HandleFunc("/api/report", makeReportPostHandler(c)).Methods("POST") } -func makeReportPostHandler(a Adder) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +// RegisterReportPostHandler registers the handler for report submission +func RegisterReportPostHandler(a Adder, router *mux.Router) { + post := router.Methods("POST").Subrouter() + post.HandleFunc("/api/report", func(w http.ResponseWriter, r *http.Request) { var ( rpt report.Report reader = r.Body @@ -101,9 +104,9 @@ func makeReportPostHandler(a Adder) http.HandlerFunc { topologyRegistry.enableKubernetesTopologies() } w.WriteHeader(http.StatusOK) - } + }) } func apiHandler(w http.ResponseWriter, r *http.Request) { - respondWith(w, http.StatusOK, xfer.Details{ID: uniqueID, Version: version}) + respondWith(w, http.StatusOK, xfer.Details{ID: UniqueID, Version: Version}) } diff --git a/app/router_test.go b/app/router_test.go index 3bc4fc3e21..8634dbe972 100644 --- a/app/router_test.go +++ b/app/router_test.go @@ -1,9 +1,10 @@ -package main +package app_test import ( "bytes" "encoding/gob" "encoding/json" + "io/ioutil" "net/http" "net/http/httptest" "reflect" @@ -12,6 +13,7 @@ import ( "github.com/gorilla/mux" + "github.com/weaveworks/scope/app" "github.com/weaveworks/scope/test" "github.com/weaveworks/scope/test/fixture" ) @@ -21,7 +23,7 @@ type v map[string]string func TestURLMatcher(t *testing.T) { test := func(pattern, path string, match bool, vars v) { routeMatch := &mux.RouteMatch{} - if URLMatcher(pattern)(&http.Request{RequestURI: path}, routeMatch) != match { + if app.URLMatcher(pattern)(&http.Request{RequestURI: path}, routeMatch) != match { t.Fatalf("'%s' '%s'", pattern, path) } if match && !reflect.DeepEqual(v(routeMatch.Vars), vars) { @@ -39,21 +41,38 @@ func TestURLMatcher(t *testing.T) { func TestReportPostHandler(t *testing.T) { test := func(contentType string, encoder func(interface{}) ([]byte, error)) { + router := mux.NewRouter() + c := app.NewCollector(1 * time.Minute) + app.RegisterReportPostHandler(c, router) + ts := httptest.NewServer(router) + defer ts.Close() + b, err := encoder(fixture.Report) if err != nil { t.Fatalf("Content-Type %s: %s", contentType, err) } - r, _ := http.NewRequest("POST", "/api/report", bytes.NewReader(b)) - r.Header.Set("Content-Type", contentType) - w := httptest.NewRecorder() - c := NewCollector(1 * time.Minute) - makeReportPostHandler(c).ServeHTTP(w, r) - if w.Code != http.StatusOK { - t.Fatalf("Content-Type %s: http status: %d\nbody: %s", contentType, w.Code, w.Body.String()) + req, err := http.NewRequest("POST", ts.URL+"/api/report", bytes.NewReader(b)) + if err != nil { + t.Fatalf("Error posting report: %v", err) + } + req.Header.Set("Content-Type", contentType) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("Error posting report %v", err) } - // Just check a few items, to confirm it parsed. Otherwise - // reflect.DeepEqual chokes on nil vs empty arrays. + + _, err = ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + t.Fatalf("Error posting report: %v", err) + } + + if resp.StatusCode != http.StatusOK { + t.Fatalf("Error posting report: %d", resp.StatusCode) + } + if want, have := fixture.Report.Endpoint.Nodes, c.Report().Endpoint.Nodes; len(have) == 0 || len(want) != len(have) { t.Fatalf("Content-Type %s: %v", contentType, test.Diff(have, want)) } diff --git a/app/scope_test.go b/app/scope_test.go index 8c166f9415..1942451fa0 100644 --- a/app/scope_test.go +++ b/app/scope_test.go @@ -1,4 +1,4 @@ -package main +package app_test import ( "bytes" diff --git a/app/server_helpers.go b/app/server_helpers.go index 8aa1fe8285..92b1221e77 100644 --- a/app/server_helpers.go +++ b/app/server_helpers.go @@ -1,4 +1,4 @@ -package main +package app import ( "encoding/json" diff --git a/app/site_test.go b/app/site_test.go deleted file mode 100644 index 09b3a96bb5..0000000000 --- a/app/site_test.go +++ /dev/null @@ -1,21 +0,0 @@ -// Basic site layout tests. -package main - -import ( - "net/http/httptest" - "testing" - - "github.com/gorilla/mux" -) - -// Test site -func TestSite(t *testing.T) { - router := mux.NewRouter() - registerStatic(router) - ts := httptest.NewServer(router) - defer ts.Close() - - is200(t, ts, "/") - is200(t, ts, "/index.html") - is404(t, ts, "/index.html/foobar") -} diff --git a/app/main.go b/prog/app/main.go similarity index 70% rename from app/main.go rename to prog/app/main.go index 2320300115..6c6bc709a5 100644 --- a/app/main.go +++ b/prog/app/main.go @@ -14,27 +14,17 @@ import ( "github.com/gorilla/mux" "github.com/weaveworks/weave/common" + "github.com/weaveworks/scope/app" "github.com/weaveworks/scope/xfer" ) -var ( - // Set at buildtime. - version = "dev" - - // Set at runtime. - uniqueID = "0" -) - -func registerStatic(router *mux.Router) { - router.Methods("GET").PathPrefix("/").Handler(http.FileServer(FS(false))) -} - // Router creates the mux for all the various app components. -func Router(c collector) *mux.Router { +func Router(c app.Collector) *mux.Router { router := mux.NewRouter() - registerTopologyRoutes(c, router) - registerControlRoutes(router) - registerStatic(router) + app.RegisterTopologyRoutes(c, router) + app.RegisterReportPostHandler(c, router) + app.RegisterControlRoutes(router) + router.Methods("GET").PathPrefix("/").Handler(http.FileServer(FS(false))) return router } @@ -48,7 +38,7 @@ func main() { flag.Parse() if *printVersion { - fmt.Println(version) + fmt.Println(app.Version) return } @@ -60,11 +50,9 @@ func main() { defer log.Print("app exiting") rand.Seed(time.Now().UnixNano()) - uniqueID = strconv.FormatInt(rand.Int63(), 16) - log.Printf("app starting, version %s, ID %s", version, uniqueID) - - c := NewCollector(*window) - http.Handle("/", Router(c)) + app.UniqueID = strconv.FormatInt(rand.Int63(), 16) + log.Printf("app starting, version %s, ID %s", app.Version, app.UniqueID) + http.Handle("/", Router(app.NewCollector(*window))) go func() { log.Printf("listening on %s", *listen) log.Print(http.ListenAndServe(*listen, nil))