// Set the list of endpoints for the given hostname. func (c *multiClient) Set(hostname string, endpoints []string) { wg := sync.WaitGroup{} wg.Add(len(endpoints)) clients := make(chan clientTuple, len(endpoints)) for _, endpoint := range endpoints { go func(endpoint string) { c.sema.acquire() defer c.sema.release() defer wg.Done() client, err := c.clientFactory(hostname, endpoint) if err != nil { log.Errorf("Error creating new app client: %v", err) return } details, err := client.Details() if err != nil { log.Errorf("Error fetching app details: %v", err) return } clients <- clientTuple{details, client} }(endpoint) } wg.Wait() close(clients) c.mtx.Lock() defer c.mtx.Unlock() // Start any new apps, and replace the list of app ids for this hostname hostIDs := report.MakeIDList() for tuple := range clients { hostIDs = hostIDs.Add(tuple.ID) _, ok := c.clients[tuple.ID] if !ok { c.clients[tuple.ID] = tuple.AppClient tuple.AppClient.ControlConnection() } } c.ids[hostname] = hostIDs // Remove apps that are no longer referenced (by id) from any hostname allReferencedIDs := report.MakeIDList() for _, ids := range c.ids { allReferencedIDs = allReferencedIDs.Add(ids...) } for id, client := range c.clients { if !allReferencedIDs.Contains(id) { client.Stop() delete(c.clients, id) } } }
func TestFilterRender(t *testing.T) { renderer := mockRenderer{Nodes: report.Nodes{ "foo": report.MakeNode("foo").WithAdjacent("bar"), "bar": report.MakeNode("bar").WithAdjacent("foo"), "baz": report.MakeNode("baz"), }} have := report.MakeIDList() for id := range renderer.Render(report.MakeReport(), render.FilterUnconnected) { have = have.Add(id) } want := report.MakeIDList("foo", "bar") if !reflect.DeepEqual(want, have) { t.Error(test.Diff(want, have)) } }
func assertAdjacent(t *testing.T, n report.Node, ids ...string) { want := report.MakeIDList(ids...) if have := n.Adjacency; !reflect.DeepEqual(want, have) { t.Fatalf("want adjacency list %v, have %v", want, have) } }
func endpointChildIDsOf(n report.Node) report.IDList { result := report.MakeIDList() n.Children.ForEach(func(child report.Node) { if child.Topology == report.Endpoint { result = result.Add(child.ID) } }) return result }
func TestIDList(t *testing.T) { have := report.MakeIDList("alpha", "mu", "zeta") have = have.Add("alpha") have = have.Add("nu") have = have.Add("mu") have = have.Add("alpha") have = have.Add("alpha") have = have.Add("epsilon") have = have.Add("delta") if want := report.IDList([]string{"alpha", "delta", "epsilon", "mu", "nu", "zeta"}); !reflect.DeepEqual(want, have) { t.Errorf("want %+v, have %+v", want, have) } }
func (f *Filter) render(rpt report.Report, dct Decorator) (report.Nodes, int) { output := report.Nodes{} inDegrees := map[string]int{} filtered := 0 for id, node := range f.Renderer.Render(rpt, dct) { if node.Topology == Pseudo || f.FilterFunc(node) { output[id] = node inDegrees[id] = 0 } else { filtered++ } } // Deleted nodes also need to be cut as destinations in adjacency lists. for id, node := range output { newAdjacency := report.MakeIDList() for _, dstID := range node.Adjacency { if _, ok := output[dstID]; ok { newAdjacency = newAdjacency.Add(dstID) inDegrees[dstID]++ } } node.Adjacency = newAdjacency output[id] = node } // Remove unconnected pseudo nodes, see #483. for id, inDegree := range inDegrees { if inDegree > 0 { continue } node := output[id] if node.Topology != Pseudo || len(node.Adjacency) > 0 { continue } delete(output, id) filtered++ } return output, filtered }
func TestMakeDetailedHostNode(t *testing.T) { renderableNodes := render.HostRenderer.Render(fixture.Report, render.FilterNoop) renderableNode := renderableNodes[fixture.ClientHostNodeID] have := detailed.MakeNode("hosts", fixture.Report, renderableNodes, renderableNode) containerImageNodeSummary := child(t, render.ContainerImageRenderer, fixture.ClientContainerImageNodeID) containerNodeSummary := child(t, render.ContainerRenderer, fixture.ClientContainerNodeID) process1NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess1NodeID) process1NodeSummary.Linkable = true process2NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess2NodeID) process2NodeSummary.Linkable = true podNodeSummary := child(t, render.PodRenderer, fixture.ClientPodNodeID) want := detailed.Node{ NodeSummary: detailed.NodeSummary{ ID: fixture.ClientHostNodeID, Label: "client", LabelMinor: "hostname.com", Rank: "hostname.com", Pseudo: false, Shape: "circle", Linkable: true, Adjacency: report.MakeIDList(fixture.ServerHostNodeID), Metadata: []report.MetadataRow{ { ID: "host_name", Label: "Hostname", Value: "client.hostname.com", Priority: 11, }, { ID: "os", Label: "OS", Value: "Linux", Priority: 12, }, { ID: "local_networks", Label: "Local Networks", Value: "10.10.10.0/24", Priority: 13, }, }, Metrics: []report.MetricRow{ { ID: host.CPUUsage, Label: "CPU", Format: "percent", Value: 0.07, Priority: 1, Metric: &fixture.ClientHostCPUMetric, }, { ID: host.MemoryUsage, Label: "Memory", Format: "filesize", Value: 0.08, Priority: 2, Metric: &fixture.ClientHostMemoryMetric, }, { ID: host.Load1, Label: "Load (1m)", Group: "load", Value: 0.09, Priority: 11, Metric: &fixture.ClientHostLoad1Metric, }, }, }, Controls: []detailed.ControlInstance{}, Children: []detailed.NodeSummaryGroup{ { Label: "Pods", TopologyID: "pods", Columns: []detailed.Column{ {ID: report.Container, Label: "# Containers"}, {ID: kubernetes.IP, Label: "IP"}, }, Nodes: []detailed.NodeSummary{podNodeSummary}, }, { Label: "Containers", TopologyID: "containers", Columns: []detailed.Column{ {ID: docker.CPUTotalUsage, Label: "CPU"}, {ID: docker.MemoryUsage, Label: "Memory"}, }, Nodes: []detailed.NodeSummary{containerNodeSummary}, }, { Label: "Processes", TopologyID: "processes", Columns: []detailed.Column{ {ID: process.PID, Label: "PID"}, {ID: process.CPUUsage, Label: "CPU"}, {ID: process.MemoryUsage, Label: "Memory"}, }, Nodes: []detailed.NodeSummary{process1NodeSummary, process2NodeSummary}, }, { Label: "Container Images", TopologyID: "containers-by-image", Columns: []detailed.Column{ {ID: report.Container, Label: "# Containers", DefaultSort: true}, }, Nodes: []detailed.NodeSummary{containerImageNodeSummary}, }, }, Connections: []detailed.ConnectionsSummary{ { ID: "incoming-connections", TopologyID: "hosts", Label: "Inbound", Columns: detailed.NormalColumns, Connections: []detailed.Connection{}, }, { ID: "outgoing-connections", TopologyID: "hosts", Label: "Outbound", Columns: detailed.NormalColumns, Connections: []detailed.Connection{ { ID: fmt.Sprintf("%s:%s-%s:%s-%d", fixture.ServerHostNodeID, "", fixture.ClientHostNodeID, "", 80), NodeID: fixture.ServerHostNodeID, Label: "server", Linkable: true, Metadata: []report.MetadataRow{ { ID: "port", Value: "80", Datatype: "number", }, { ID: "count", Value: "2", Datatype: "number", }, }, }, }, }, }, } if !reflect.DeepEqual(want, have) { t.Errorf("%s", test.Diff(want, have)) } }
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)) } } }
func TestMakeNodeSummary(t *testing.T) { testcases := []struct { name string input report.Node ok bool want detailed.NodeSummary }{ { name: "single process rendering", input: expected.RenderedProcesses[fixture.ClientProcess1NodeID], ok: true, want: detailed.NodeSummary{ ID: fixture.ClientProcess1NodeID, Label: fixture.Client1Name, LabelMinor: "client.hostname.com (10001)", Rank: fixture.Client1Name, Shape: "square", Metadata: []report.MetadataRow{ {ID: process.PID, Label: "PID", Value: fixture.Client1PID, Priority: 1, Datatype: "number"}, }, Adjacency: report.MakeIDList(fixture.ServerProcessNodeID), }, }, { name: "single container rendering", input: expected.RenderedContainers[fixture.ClientContainerNodeID], ok: true, want: detailed.NodeSummary{ ID: fixture.ClientContainerNodeID, Label: fixture.ClientContainerName, LabelMinor: fixture.ClientHostName, Rank: fixture.ClientContainerImageName, Shape: "hexagon", Linkable: true, Metadata: []report.MetadataRow{ {ID: docker.ContainerID, Label: "ID", Value: fixture.ClientContainerID, Priority: 1}, }, Adjacency: report.MakeIDList(fixture.ServerContainerNodeID), }, }, { name: "single container image rendering", input: expected.RenderedContainerImages[fixture.ClientContainerImageNodeID], ok: true, want: detailed.NodeSummary{ ID: fixture.ClientContainerImageNodeID, Label: fixture.ClientContainerImageName, LabelMinor: "1 container", Rank: fixture.ClientContainerImageName, Shape: "hexagon", Linkable: true, Stack: true, Metadata: []report.MetadataRow{ {ID: docker.ImageID, Label: "Image ID", Value: fixture.ClientContainerImageID, Priority: 1}, {ID: report.Container, Label: "# Containers", Value: "1", Priority: 2, Datatype: "number"}, }, Adjacency: report.MakeIDList(fixture.ServerContainerImageNodeID), }, }, { name: "single host rendering", input: expected.RenderedHosts[fixture.ClientHostNodeID], ok: true, want: detailed.NodeSummary{ ID: fixture.ClientHostNodeID, Label: "client", LabelMinor: "hostname.com", Rank: "hostname.com", Shape: "circle", Linkable: true, Metadata: []report.MetadataRow{ {ID: host.HostName, Label: "Hostname", Value: fixture.ClientHostName, Priority: 11}, }, Adjacency: report.MakeIDList(fixture.ServerHostNodeID), }, }, { name: "group node rendering", input: expected.RenderedProcessNames[fixture.ServerName], ok: true, want: detailed.NodeSummary{ ID: "apache", Label: "apache", LabelMinor: "1 process", Rank: "apache", Shape: "square", Stack: true, Linkable: true, }, }, } for _, testcase := range testcases { have, ok := detailed.MakeNodeSummary(fixture.Report, testcase.input) if ok != testcase.ok { t.Errorf("%s: MakeNodeSummary failed: expected ok value to be: %v", testcase.name, testcase.ok) continue } if !reflect.DeepEqual(testcase.want, have) { t.Errorf("%s: Node Summary did not match: %s", testcase.name, test.Diff(testcase.want, have)) } } }