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. 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. // 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. func (n *Nodes) UnsafeMerge(other Nodes) { for k, v := range other { if existing, ok := (*n)[k]; ok { // don't overwrite (*n)[k] = v.Merge(existing) } 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 }