mirror of
https://github.com/weaveworks/scope.git
synced 2026-05-06 09:18:27 +00:00
316 lines
9.5 KiB
Go
316 lines
9.5 KiB
Go
package report
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Topology describes a specific view of a network. It consists of
|
|
// nodes with metadata, and edges. Edges are directional, and embedded
|
|
// in the Node struct.
|
|
type Topology struct {
|
|
Shape string `json:"shape,omitempty"`
|
|
Tag string `json:"tag,omitempty"`
|
|
Label string `json:"label,omitempty"`
|
|
LabelPlural string `json:"label_plural,omitempty"`
|
|
Nodes Nodes `json:"nodes,omitempty" deepequal:"nil==empty"`
|
|
Controls Controls `json:"controls,omitempty" deepequal:"nil==empty"`
|
|
MetadataTemplates MetadataTemplates `json:"metadata_templates,omitempty"`
|
|
MetricTemplates MetricTemplates `json:"metric_templates,omitempty"`
|
|
TableTemplates TableTemplates `json:"table_templates,omitempty"`
|
|
}
|
|
|
|
// MakeTopology gives you a Topology.
|
|
func MakeTopology() Topology {
|
|
return Topology{
|
|
Nodes: map[string]Node{},
|
|
Controls: Controls{},
|
|
}
|
|
}
|
|
|
|
// WithMetadataTemplates merges some metadata templates into this topology,
|
|
// returning a new topology.
|
|
func (t Topology) WithMetadataTemplates(other MetadataTemplates) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Merge(other),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// WithMetricTemplates merges some metadata templates into this topology,
|
|
// returning a new topology.
|
|
func (t Topology) WithMetricTemplates(other MetricTemplates) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Merge(other),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// WithTableTemplates merges some table templates into this topology,
|
|
// returning a new topology.
|
|
func (t Topology) WithTableTemplates(other TableTemplates) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Merge(other),
|
|
}
|
|
}
|
|
|
|
// WithShape sets the shape of nodes from this topology, returning a new topology.
|
|
func (t Topology) WithShape(shape string) Topology {
|
|
return Topology{
|
|
Shape: shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// WithTag sets the tag of nodes from this topology, returning a new topology.
|
|
func (t Topology) WithTag(tag string) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// WithLabel sets the label terminology of this topology, returning a new topology.
|
|
func (t Topology) WithLabel(label, labelPlural string) Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: label,
|
|
LabelPlural: labelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// AddNode adds node to the topology under key nodeID; if a
|
|
// node already exists for this key, nmd is merged with that node.
|
|
// This method is different from all the other similar methods
|
|
// in that it mutates the Topology, to solve issues of GC pressure.
|
|
func (t Topology) AddNode(node Node) {
|
|
if existing, ok := t.Nodes[node.ID]; ok {
|
|
node = node.Merge(existing)
|
|
}
|
|
t.Nodes[node.ID] = node
|
|
}
|
|
|
|
// ReplaceNode adds node to the topology under key nodeID; if a
|
|
// node already exists for this key, node replaces that node.
|
|
// Like AddNode, it mutates the Topology
|
|
func (t Topology) ReplaceNode(node Node) {
|
|
t.Nodes[node.ID] = node
|
|
}
|
|
|
|
// GetShape returns the current topology shape, or the default if there isn't one.
|
|
func (t Topology) GetShape() string {
|
|
if t.Shape == "" {
|
|
return Circle
|
|
}
|
|
return t.Shape
|
|
}
|
|
|
|
// Copy returns a value copy of the Topology.
|
|
func (t Topology) Copy() Topology {
|
|
return Topology{
|
|
Shape: t.Shape,
|
|
Tag: t.Tag,
|
|
Label: t.Label,
|
|
LabelPlural: t.LabelPlural,
|
|
Nodes: t.Nodes.Copy(),
|
|
Controls: t.Controls.Copy(),
|
|
MetadataTemplates: t.MetadataTemplates.Copy(),
|
|
MetricTemplates: t.MetricTemplates.Copy(),
|
|
TableTemplates: t.TableTemplates.Copy(),
|
|
}
|
|
}
|
|
|
|
// Merge merges the other object into this one, and returns the result object.
|
|
// The original is not modified.
|
|
func (t Topology) Merge(other Topology) Topology {
|
|
shape := t.Shape
|
|
if shape == "" {
|
|
shape = other.Shape
|
|
}
|
|
label, labelPlural := t.Label, t.LabelPlural
|
|
if label == "" {
|
|
label, labelPlural = other.Label, other.LabelPlural
|
|
}
|
|
tag := t.Tag
|
|
if tag == "" {
|
|
tag = other.Tag
|
|
}
|
|
return Topology{
|
|
Shape: shape,
|
|
Tag: tag,
|
|
Label: label,
|
|
LabelPlural: labelPlural,
|
|
Nodes: t.Nodes.Merge(other.Nodes),
|
|
Controls: t.Controls.Merge(other.Controls),
|
|
MetadataTemplates: t.MetadataTemplates.Merge(other.MetadataTemplates),
|
|
MetricTemplates: t.MetricTemplates.Merge(other.MetricTemplates),
|
|
TableTemplates: t.TableTemplates.Merge(other.TableTemplates),
|
|
}
|
|
}
|
|
|
|
// UnsafeMerge merges the other object into this one, modifying the original.
|
|
// 't' is taken to be older, and Node string values in 'other' will override
|
|
func (t *Topology) UnsafeMerge(other Topology) {
|
|
if t.Shape == "" {
|
|
t.Shape = other.Shape
|
|
}
|
|
if t.Label == "" {
|
|
t.Label, t.LabelPlural = other.Label, other.LabelPlural
|
|
}
|
|
if t.Tag == "" {
|
|
t.Tag = other.Tag
|
|
}
|
|
t.Nodes.UnsafeMerge(other.Nodes)
|
|
t.Controls = t.Controls.Merge(other.Controls)
|
|
t.MetadataTemplates = t.MetadataTemplates.Merge(other.MetadataTemplates)
|
|
t.MetricTemplates = t.MetricTemplates.Merge(other.MetricTemplates)
|
|
t.TableTemplates = t.TableTemplates.Merge(other.TableTemplates)
|
|
}
|
|
|
|
// UnsafeUnMerge removes any information from t that would be added by merging other,
|
|
// modifying the original.
|
|
func (t *Topology) UnsafeUnMerge(other Topology) {
|
|
if t.Shape == other.Shape {
|
|
t.Shape = ""
|
|
}
|
|
if t.Label == other.Label && t.LabelPlural == other.LabelPlural {
|
|
t.Label, t.LabelPlural = "", ""
|
|
}
|
|
if t.Tag == other.Tag {
|
|
t.Tag = ""
|
|
}
|
|
t.Nodes.UnsafeUnMerge(other.Nodes)
|
|
// TODO Controls
|
|
// NOTE: taking a shortcut and assuming templates are static, which they have always been in Scope
|
|
// If you break that assumption please change this.
|
|
t.MetadataTemplates = nil
|
|
t.MetricTemplates = nil
|
|
t.TableTemplates = nil
|
|
}
|
|
|
|
// Nodes is a collection of nodes in a topology. Keys are node IDs.
|
|
// TODO(pb): type Topology map[string]Node
|
|
type Nodes map[string]Node
|
|
|
|
// Copy returns a value copy of the Nodes.
|
|
func (n Nodes) Copy() Nodes {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
cp := make(Nodes, len(n))
|
|
for k, v := range n {
|
|
cp[k] = v
|
|
}
|
|
return cp
|
|
}
|
|
|
|
// Merge merges the other object into this one, and returns the result object.
|
|
// 'n' is taken to be older, and Node string values in 'other' will override
|
|
// The original is not modified.
|
|
func (n Nodes) Merge(other Nodes) Nodes {
|
|
if len(other) > len(n) {
|
|
n, other = other, n
|
|
}
|
|
if len(other) == 0 {
|
|
return n
|
|
}
|
|
cp := n.Copy()
|
|
cp.UnsafeMerge(other)
|
|
return cp
|
|
}
|
|
|
|
// UnsafeMerge merges the other object into this one, modifying the original.
|
|
// 'n' is taken to be older, and Node string values in 'other' will override
|
|
func (n *Nodes) UnsafeMerge(other Nodes) {
|
|
for k, v := range other {
|
|
if existing, ok := (*n)[k]; ok { // don't overwrite
|
|
(*n)[k] = existing.Merge(v)
|
|
} else {
|
|
(*n)[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
// UnsafeUnMerge removes nodes from n that would be added by merging other,
|
|
// modifying the original.
|
|
func (n *Nodes) UnsafeUnMerge(other Nodes) {
|
|
for k, node := range *n {
|
|
if otherNode, ok := (other)[k]; ok {
|
|
remove := node.UnsafeUnMerge(otherNode)
|
|
if remove {
|
|
delete(*n, k)
|
|
} else {
|
|
(*n)[k] = node
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate checks the topology for various inconsistencies.
|
|
func (t Topology) Validate() error {
|
|
errs := []string{}
|
|
|
|
// Check all nodes are valid, and the keys are parseable, i.e.
|
|
// contain a scope.
|
|
for nodeID, nmd := range t.Nodes {
|
|
if _, _, ok := ParseNodeID(nodeID); !ok {
|
|
errs = append(errs, fmt.Sprintf("invalid node ID %q", nodeID))
|
|
}
|
|
|
|
// Check all adjancency keys has entries in Node.
|
|
for _, dstNodeID := range nmd.Adjacency {
|
|
if _, ok := t.Nodes[dstNodeID]; !ok {
|
|
errs = append(errs, fmt.Sprintf("node missing from adjacency %q -> %q", nodeID, dstNodeID))
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(errs) > 0 {
|
|
return fmt.Errorf("%d error(s): %s", len(errs), strings.Join(errs, "; "))
|
|
}
|
|
|
|
return nil
|
|
}
|