// Websocket for the full topology. func handleWebsocket( ctx context.Context, rep Reporter, w http.ResponseWriter, r *http.Request, ) { if err := r.ParseForm(); err != nil { respondWith(w, http.StatusInternalServerError, err.Error()) return } loop := websocketLoop if t := r.Form.Get("t"); t != "" { var err error if loop, err = time.ParseDuration(t); err != nil { respondWith(w, http.StatusBadRequest, t) return } } conn, err := xfer.Upgrade(w, r, nil) if err != nil { // log.Info("Upgrade:", err) return } defer conn.Close() quit := make(chan struct{}) go func(c xfer.Websocket) { for { // just discard everything the browser sends if _, _, err := c.ReadMessage(); err != nil { if !xfer.IsExpectedWSCloseError(err) { log.Println("err:", err) } close(quit) break } } }(conn) var ( previousTopo detailed.NodeSummaries tick = time.Tick(loop) wait = make(chan struct{}, 1) topologyID = mux.Vars(r)["topology"] ) rep.WaitOn(ctx, wait) defer rep.UnWait(ctx, wait) for { report, err := rep.Report(ctx) if err != nil { log.Errorf("Error generating report: %v", err) return } renderer, decorator, err := topologyRegistry.rendererForTopology(topologyID, r.Form, 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 if err := conn.WriteJSON(diff); err != nil { if !xfer.IsExpectedWSCloseError(err) { log.Errorf("cannot serialize topology diff: %s", err) } return } select { case <-wait: case <-tick: case <-quit: return } } }
func TestTopoDiff(t *testing.T) { nodea := detailed.NodeSummary{ ID: "nodea", Label: "Node A", LabelMinor: "'ts an a", Pseudo: false, Adjacency: report.MakeIDList("nodeb"), } nodeap := nodea.Copy() nodeap.Adjacency = report.MakeIDList("nodeb", "nodeq") // not the same anymore nodeb := detailed.NodeSummary{ ID: "nodeb", Label: "Node B", } // Helper to make RenderableNode maps. nodes := func(ns ...detailed.NodeSummary) detailed.NodeSummaries { r := detailed.NodeSummaries{} for _, n := range ns { r[n.ID] = n } return r } for _, c := range []struct { label string have, want detailed.Diff }{ { label: "basecase: empty -> something", have: detailed.TopoDiff(nodes(), nodes(nodea, nodeb)), want: detailed.Diff{ Add: []detailed.NodeSummary{nodea, nodeb}, }, }, { label: "basecase: something -> empty", have: detailed.TopoDiff(nodes(nodea, nodeb), nodes()), want: detailed.Diff{ Remove: []string{"nodea", "nodeb"}, }, }, { label: "add and remove", have: detailed.TopoDiff(nodes(nodea), nodes(nodeb)), want: detailed.Diff{ Add: []detailed.NodeSummary{nodeb}, Remove: []string{"nodea"}, }, }, { label: "no change", have: detailed.TopoDiff(nodes(nodea), nodes(nodea)), want: detailed.Diff{}, }, { label: "change a single node", have: detailed.TopoDiff(nodes(nodea), nodes(nodeap)), want: detailed.Diff{ Update: []detailed.NodeSummary{nodeap}, }, }, } { sort.Strings(c.have.Remove) sort.Sort(ByID(c.have.Add)) sort.Sort(ByID(c.have.Update)) if !reflect.DeepEqual(c.want, c.have) { t.Errorf("%s - %s", c.label, test.Diff(c.want, c.have)) } } }