Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connection tables to details panel #1017

Merged
merged 6 commits into from
Mar 4, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions app/api_topology.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,18 @@ func handleWs(ctx context.Context, rep Reporter, renderer render.Renderer, w htt
}

// Individual nodes.
func handleNode(nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) {
func handleNode(topologyID, nodeID string) func(context.Context, Reporter, render.Renderer, http.ResponseWriter, *http.Request) {
return func(ctx context.Context, rep Reporter, renderer render.Renderer, w http.ResponseWriter, r *http.Request) {
var (
rpt = rep.Report(ctx)
node, ok = renderer.Render(rep.Report(ctx))[nodeID]
rendered = renderer.Render(rep.Report(ctx))
node, ok = rendered[nodeID]
)
if !ok {
http.NotFound(w, r)
return
}
respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(rpt, node)})
respondWith(w, http.StatusOK, APINode{Node: detailed.MakeNode(topologyID, rpt, rendered, node)})
}
}

Expand Down
25 changes: 2 additions & 23 deletions app/api_topology_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,27 +54,6 @@ func TestAll(t *testing.T) {
}
}

func TestAPITopologyContainers(t *testing.T) {
ts := topologyServer()
{
body := getRawJSON(t, ts, "/api/topology/containers")
var topo app.APITopology
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
if err := decoder.Decode(&topo); err != nil {
t.Fatal(err)
}
want := expected.RenderedContainers.Copy()
for id, node := range want {
node.ControlNode = ""
want[id] = node
}

if have := topo.Nodes.Prune(); !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
}

func TestAPITopologyProcesses(t *testing.T) {
ts := topologyServer()

This comment was marked as abuse.

defer ts.Close()
Expand Down Expand Up @@ -124,13 +103,13 @@ func TestAPITopologyHosts(t *testing.T) {
}
}
{
body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostRenderedID)
body := getRawJSON(t, ts, "/api/topology/hosts/"+expected.ServerHostID)
var node app.APINode
decoder := codec.NewDecoderBytes(body, &codec.JsonHandle{})
if err := decoder.Decode(&node); err != nil {
t.Fatal(err)
}
equals(t, expected.ServerHostRenderedID, node.Node.ID)
equals(t, expected.ServerHostID, node.Node.ID)
equals(t, "server", node.Node.Label)
equals(t, false, node.Node.Pseudo)
// Let's not unit-test the specific content of the detail tables
Expand Down
2 changes: 1 addition & 1 deletion app/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func TopologyHandler(c Reporter, preRoutes *mux.Router, postRoutes http.Handler)
}

handler := gzipHandler(requestContextDecorator(topologyRegistry.captureRendererWithoutFilters(
c, topologyID, handleNode(nodeID),
c, topologyID, handleNode(topologyID, nodeID),
)))
handler.ServeHTTP(w, r)
})
Expand Down
8 changes: 8 additions & 0 deletions client/app/scripts/components/node-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ export default class NodeDetails extends React.Component {
<NodeDetailsInfo rows={details.metadata} />
</div>}

{details.connections && details.connections.map(connections => {
return (
<div className="node-details-content-section" key={connections.id}>
<NodeDetailsTable {...connections} />
</div>
);
})}

{details.children && details.children.map(children => {
return (
<div className="node-details-content-section" key={children.topologyId}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import ShowMore from '../show-more';
import NodeDetailsTableNodeLink from './node-details-table-node-link';
import NodeDetailsTableNodeMetric from './node-details-table-node-metric';

function isNumberField(field) {
return field.dataType && field.dataType === 'number';
}

export default class NodeDetailsTable extends React.Component {

constructor(props, context) {
Expand Down Expand Up @@ -32,14 +36,28 @@ export default class NodeDetailsTable extends React.Component {
}

getDefaultSortBy() {
// first metric
// default sorter specified by columns
const defaultSortColumn = _.find(this.props.columns, {defaultSort: true});
if (defaultSortColumn) {
return defaultSortColumn.id;
}
// otherwise choose first metric
return _.get(this.props.nodes, [0, 'metrics', 0, 'id']);
}

getMetaDataSorters() {
// returns an array of sorters that will take a node
return _.get(this.props.nodes, [0, 'metadata'], []).map((field, index) => {
return node => node.metadata[index] ? node.metadata[index].value : null;
return node => {
const nodeMetadataField = node.metadata[index];
if (nodeMetadataField) {
if (isNumberField(nodeMetadataField)) {
return parseFloat(nodeMetadataField.value);
}
return nodeMetadataField.value;
}
return null;
};
});
}

Expand All @@ -49,6 +67,9 @@ export default class NodeDetailsTable extends React.Component {
if (sortBy !== null) {
const field = _.union(node.metrics, node.metadata).find(f => f.id === sortBy);
if (field) {
if (isNumberField(field)) {
return parseFloat(field.value);
}
return field.value;
}
}
Expand Down
6 changes: 3 additions & 3 deletions integration/300_internet_edge_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ do_connections() {
}
do_connections&

wait_for_containers $HOST1 60 nginx "Inbound"
wait_for_containers $HOST1 60 nginx "The Internet"

has_container $HOST1 nginx
has_container $HOST1 "Inbound"
has_connection containers $HOST1 "Inbound" nginx
has_container $HOST1 "The Internet"
has_connection containers $HOST1 "The Internet" nginx

kill %do_connections

Expand Down
197 changes: 197 additions & 0 deletions render/detailed/connections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package detailed

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.


import (
"sort"
"strconv"

"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
)

const (
portKey = "port"
portLabel = "Port"
countKey = "count"
countLabel = "Count"
number = "number"
)

// Exported for testing
var (
NormalColumns = []Column{
{ID: portKey, Label: portLabel},
{ID: countKey, Label: countLabel, DefaultSort: true},
}
InternetColumns = []Column{
{ID: "foo", Label: "Remote"},
{ID: portKey, Label: portLabel},
{ID: countKey, Label: countLabel, DefaultSort: true},
}
)

func endpointChildrenOf(n render.RenderableNode) []render.RenderableNode {
result := []render.RenderableNode{}
n.Children.ForEach(func(child render.RenderableNode) {
if _, _, _, ok := render.ParseEndpointID(child.ID); ok {
result = append(result, child)
}
})
return result
}

func endpointChildIDsOf(n render.RenderableNode) report.IDList {
result := report.MakeIDList()
n.Children.ForEach(func(child render.RenderableNode) {
if _, _, _, ok := render.ParseEndpointID(child.ID); ok {
result = append(result, child.ID)
}
})
return result
}

type connectionsRow struct {
remoteNode, localNode *render.RenderableNode
remoteAddr, localAddr string
port string // always the server-side port
}

func buildConnectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary {
nodes := []NodeSummary{}
for row, count := range in {
id, label, linkable := row.remoteNode.ID, row.remoteNode.LabelMajor, true
if row.remoteAddr != "" {
id, label, linkable = row.remoteAddr+":"+row.port, row.remoteAddr, false
}
metadata := []MetadataRow{}
if includeLocal {
metadata = append(metadata,
MetadataRow{
ID: "foo",
Value: row.localAddr,
Datatype: number,
})
}
metadata = append(metadata,
MetadataRow{
ID: portKey,
Value: row.port,
Datatype: number,
},
MetadataRow{
ID: countKey,
Value: strconv.Itoa(count),
Datatype: number,
},
)
nodes = append(nodes, NodeSummary{
ID: id,
Label: label,
Linkable: linkable,
Metadata: metadata,
})
}
sort.Sort(nodeSummariesByID(nodes))
return nodes
}

func isInternetNode(n render.RenderableNode) bool {
return n.ID == render.IncomingInternetID || n.ID == render.OutgoingInternetID
}

func makeIncomingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
localEndpointIDs := endpointChildIDsOf(n)

// For each node which has an edge TO me
counts := map[connectionsRow]int{}
for _, node := range ns {
if !node.Adjacency.Contains(n.ID) {
continue
}
remoteNode := node.Copy()

// Work out what port they are talking to, and count the number of
// connections to that port.
// This is complicated as for internet nodes we break out individual
// address, both when the internet node is remote (an incoming
// connection from the internet) and 'local' (ie you are loading
// details on the internet node)
for _, child := range endpointChildrenOf(node) {
for _, localEndpointID := range child.Adjacency.Intersection(localEndpointIDs) {
_, localAddr, port, ok := render.ParseEndpointID(localEndpointID)
if !ok {
continue
}
key := connectionsRow{
localNode: &n,
remoteNode: &remoteNode,
port: port,
}
if isInternetNode(n) {
key.localAddr = localAddr
}
counts[key] = counts[key] + 1
}
}
}

columnHeaders := NormalColumns
if isInternetNode(n) {
columnHeaders = InternetColumns
}
return NodeSummaryGroup{
ID: "incoming-connections",
TopologyID: topologyID,
Label: "Inbound",
Columns: columnHeaders,
Nodes: buildConnectionRows(counts, isInternetNode(n)),
}
}

func makeOutgoingConnectionsTable(topologyID string, n render.RenderableNode, ns render.RenderableNodes) NodeSummaryGroup {
localEndpoints := endpointChildrenOf(n)

// For each node which has an edge FROM me
counts := map[connectionsRow]int{}
for _, node := range ns {
if !n.Adjacency.Contains(node.ID) {
continue
}
remoteNode := node.Copy()
remoteEndpointIDs := endpointChildIDsOf(remoteNode)

for _, localEndpoint := range localEndpoints {
_, localAddr, _, ok := render.ParseEndpointID(localEndpoint.ID)
if !ok {
continue
}

for _, remoteEndpointID := range localEndpoint.Adjacency.Intersection(remoteEndpointIDs) {
_, _, port, ok := render.ParseEndpointID(remoteEndpointID)
if !ok {
continue
}
key := connectionsRow{
localNode: &n,
remoteNode: &remoteNode,
port: port,
}
if isInternetNode(n) {
key.localAddr = localAddr
}
counts[key] = counts[key] + 1
}
}
}

columnHeaders := NormalColumns
if isInternetNode(n) {
columnHeaders = InternetColumns
}
return NodeSummaryGroup{
ID: "outgoing-connections",
TopologyID: topologyID,
Label: "Outbound",
Columns: columnHeaders,
Nodes: buildConnectionRows(counts, isInternetNode(n)),
}
}
Loading