Files
weave-scope/probe/overlay/weave.go
Bryan Boreham 871751873b Stop render package depending on probe
This dependency makes it harder to see the structure of the program,
and sometimes complicates compilation.

Mostly just changing the source of strings that are already exported
from the report package.  A few new strings have to be moved there,
plus the function `IsPauseImageName()`.
2019-09-15 17:03:04 +00:00

394 lines
13 KiB
Go

package overlay
import (
"fmt"
"regexp"
"strings"
"sync"
"time"
"github.com/weaveworks/common/backoff"
"github.com/weaveworks/scope/common/weave"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/report"
)
// Keys for use in Node
const (
WeavePeerName = report.WeavePeerName
WeavePeerNickName = report.WeavePeerNickName
WeaveDNSHostname = "weave_dns_hostname"
WeaveMACAddress = "weave_mac_address"
WeaveVersion = "weave_version"
WeaveEncryption = "weave_encryption"
WeaveProtocol = "weave_protocol"
WeavePeerDiscovery = "weave_peer_discovery"
WeaveTargetCount = "weave_target_count"
WeaveConnectionCount = "weave_connection_count"
WeavePeerCount = "weave_peer_count"
WeaveTrustedSubnets = "weave_trusted_subnet_count"
WeaveIPAMTableID = "weave_ipam_table"
WeaveIPAMStatus = "weave_ipam_status"
WeaveIPAMRange = "weave_ipam_range"
WeaveIPAMDefaultSubnet = "weave_ipam_default_subnet"
WeaveDNSTableID = "weave_dns_table"
WeaveDNSDomain = "weave_dns_domain"
WeaveDNSUpstream = "weave_dns_upstream"
WeaveDNSTTL = "weave_dns_ttl"
WeaveDNSEntryCount = "weave_dns_entry_count"
WeaveProxyTableID = "weave_proxy_table"
WeaveProxyStatus = "weave_proxy_status"
WeaveProxyAddress = "weave_proxy_address"
WeavePluginTableID = "weave_plugin_table"
WeavePluginStatus = "weave_plugin_status"
WeavePluginDriver = "weave_plugin_driver"
WeaveConnectionsConnection = "weave_connection_connection"
WeaveConnectionsState = "weave_connection_state"
WeaveConnectionsInfo = "weave_connection_info"
WeaveConnectionsTablePrefix = "weave_connections_table_"
WeaveConnectionsMulticolumnTablePrefix = "weave_connections_multicolumn_table_"
)
var (
containerNotRunningRE = regexp.MustCompile(`Container .* is not running\n`)
containerMetadata = report.MetadataTemplates{
WeaveMACAddress: {ID: WeaveMACAddress, Label: "Weave MAC", From: report.FromLatest, Priority: 17},
WeaveDNSHostname: {ID: WeaveDNSHostname, Label: "Weave DNS Name", From: report.FromLatest, Priority: 18},
}
weaveMetadata = report.MetadataTemplates{
WeaveVersion: {ID: WeaveVersion, Label: "Version", From: report.FromLatest, Priority: 1},
WeaveProtocol: {ID: WeaveProtocol, Label: "Protocol", From: report.FromLatest, Priority: 2},
WeavePeerName: {ID: WeavePeerName, Label: "Name", From: report.FromLatest, Priority: 3},
WeaveEncryption: {ID: WeaveEncryption, Label: "Encryption", From: report.FromLatest, Priority: 4},
WeavePeerDiscovery: {ID: WeavePeerDiscovery, Label: "Peer discovery", From: report.FromLatest, Priority: 5},
WeaveTargetCount: {ID: WeaveTargetCount, Label: "Targets", From: report.FromLatest, Priority: 6},
WeaveConnectionCount: {ID: WeaveConnectionCount, Label: "Connections", From: report.FromLatest, Priority: 8},
WeavePeerCount: {ID: WeavePeerCount, Label: "Peers", From: report.FromLatest, Priority: 7},
WeaveTrustedSubnets: {ID: WeaveTrustedSubnets, Label: "Trusted subnets", From: report.FromSets, Priority: 9},
}
weaveTableTemplates = report.TableTemplates{
WeaveIPAMTableID: {
ID: WeaveIPAMTableID,
Label: "IPAM",
Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveIPAMStatus: "Status",
WeaveIPAMRange: "Range",
WeaveIPAMDefaultSubnet: "Default subnet",
},
},
WeaveDNSTableID: {
ID: WeaveDNSTableID,
Label: "DNS",
Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveDNSDomain: "Domain",
WeaveDNSUpstream: "Upstream",
WeaveDNSTTL: "TTL",
WeaveDNSEntryCount: "Entries",
},
},
WeaveProxyTableID: {
ID: WeaveProxyTableID,
Label: "Proxy",
Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveProxyStatus: "Status",
WeaveProxyAddress: "Address",
},
},
WeavePluginTableID: {
ID: WeavePluginTableID,
Label: "Plugin",
Type: report.PropertyListType,
FixedRows: map[string]string{
WeavePluginStatus: "Status",
WeavePluginDriver: "Driver name",
},
},
WeaveConnectionsMulticolumnTablePrefix: {
ID: WeaveConnectionsMulticolumnTablePrefix,
Type: report.MulticolumnTableType,
Prefix: WeaveConnectionsMulticolumnTablePrefix,
Columns: []report.Column{
{
ID: WeaveConnectionsConnection,
Label: "Connections",
},
{
ID: WeaveConnectionsState,
Label: "State",
},
{
ID: WeaveConnectionsInfo,
Label: "Info",
},
},
},
// Kept for backward-compatibility.
WeaveConnectionsTablePrefix: {
ID: WeaveConnectionsTablePrefix,
Label: "Connections",
Type: report.PropertyListType,
Prefix: WeaveConnectionsTablePrefix,
},
}
)
// Weave represents a single Weave router, presumably on the same host
// as the probe. It is both a Reporter and a Tagger: it produces an Overlay
// topology, and (in theory) can tag existing topologies with foreign keys to
// overlay -- though I'm not sure what that would look like in practice right
// now.
type Weave struct {
client weave.Client
hostID string
mtx sync.RWMutex
statusCache weave.Status
backoff backoff.Interface
psBackoff backoff.Interface
}
// NewWeave returns a new Weave tagger based on the Weave router at
// address. The address should be an IP or FQDN, no port.
func NewWeave(hostID string, client weave.Client) (*Weave, error) {
w := &Weave{
client: client,
hostID: hostID,
}
w.backoff = backoff.New(w.status, "collecting weave status")
w.backoff.SetInitialBackoff(5 * time.Second)
go w.backoff.Start()
return w, nil
}
// Name of this reporter/tagger/ticker, for metrics gathering
func (*Weave) Name() string { return "Weave" }
// Stop gathering weave status.
func (w *Weave) Stop() {
w.backoff.Stop()
}
func (w *Weave) status() (bool, error) {
status, err := w.client.Status()
w.mtx.Lock()
defer w.mtx.Unlock()
if err != nil {
w.statusCache = weave.Status{}
} else {
w.statusCache = status
}
return false, err
}
// Tag implements Tagger.
func (w *Weave) Tag(r report.Report) (report.Report, error) {
w.mtx.RLock()
defer w.mtx.RUnlock()
// Put information from weaveDNS on the container nodes
if w.statusCache.DNS != nil {
for _, entry := range w.statusCache.DNS.Entries {
if entry.Tombstone > 0 {
continue
}
nodeID := report.MakeContainerNodeID(entry.ContainerID)
node, ok := r.Container.Nodes[nodeID]
if !ok {
continue
}
w, _ := node.Latest.Lookup(WeaveDNSHostname)
hostnames := report.IDList(strings.Fields(w))
hostnames = hostnames.Add(strings.TrimSuffix(entry.Hostname, "."))
r.Container.Nodes[nodeID] = node.WithLatests(map[string]string{WeaveDNSHostname: strings.Join(hostnames, " ")})
}
}
// Put information from weave ps on the container nodes
const maxPrefixSize = 12
for id, node := range r.Container.Nodes {
prefix, ok := node.Latest.Lookup(docker.ContainerID)
if !ok {
continue
}
if len(prefix) > maxPrefixSize {
prefix = prefix[:maxPrefixSize]
}
r.Container.Nodes[id] = node
}
return r, nil
}
// Report implements Reporter.
func (w *Weave) Report() (report.Report, error) {
w.mtx.RLock()
defer w.mtx.RUnlock()
r := report.MakeReport()
r.Container = r.Container.WithMetadataTemplates(containerMetadata)
r.Overlay = r.Overlay.WithMetadataTemplates(weaveMetadata).WithTableTemplates(weaveTableTemplates)
// We report nodes for all peers (not just the current node) to highlight peers not monitored by Scope
// (i.e. without a running probe)
// Note: this will cause redundant information (n^2) if all peers have a running probe
for _, peer := range w.statusCache.Router.Peers {
node := w.getPeerNode(peer)
r.Overlay.AddNode(node)
}
if w.statusCache.IPAM != nil {
r.Overlay.AddNode(
report.MakeNode(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, w.statusCache.Router.Name)).
WithSet(report.HostLocalNetworks, report.MakeStringSet(w.statusCache.IPAM.DefaultSubnet)),
)
}
return r, nil
}
// getPeerNode obtains an Overlay topology node for representing a peer in the Weave network
func (w *Weave) getPeerNode(peer weave.Peer) report.Node {
node := report.MakeNode(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, peer.Name))
latests := map[string]string{
WeavePeerName: peer.Name,
WeavePeerNickName: peer.NickName,
}
// Peer corresponding to current host
if peer.Name == w.statusCache.Router.Name {
latests, node = w.addCurrentPeerInfo(latests, node)
}
for _, conn := range peer.Connections {
if conn.Outbound {
node = node.WithAdjacent(report.MakeOverlayNodeID(report.WeaveOverlayPeerPrefix, conn.Name))
}
}
return node.WithLatests(latests)
}
// addCurrentPeerInfo adds information exclusive to the Overlay topology node representing current Weave Net peer
// (i.e. in the same host as the reporting Scope probe)
func (w *Weave) addCurrentPeerInfo(latests map[string]string, node report.Node) (map[string]string, report.Node) {
latests[report.HostNodeID] = w.hostID
latests[WeaveVersion] = w.statusCache.Version
latests[WeaveEncryption] = "disabled"
if w.statusCache.Router.Encryption {
latests[WeaveEncryption] = "enabled"
}
latests[WeavePeerDiscovery] = "disabled"
if w.statusCache.Router.PeerDiscovery {
latests[WeavePeerDiscovery] = "enabled"
}
if w.statusCache.Router.ProtocolMinVersion == w.statusCache.Router.ProtocolMaxVersion {
latests[WeaveProtocol] = fmt.Sprintf("%d", w.statusCache.Router.ProtocolMinVersion)
} else {
latests[WeaveProtocol] = fmt.Sprintf("%d..%d", w.statusCache.Router.ProtocolMinVersion, w.statusCache.Router.ProtocolMaxVersion)
}
latests[WeaveTargetCount] = fmt.Sprintf("%d", len(w.statusCache.Router.Targets))
latests[WeaveConnectionCount] = fmt.Sprintf("%d", len(w.statusCache.Router.Connections))
latests[WeavePeerCount] = fmt.Sprintf("%d", len(w.statusCache.Router.Peers))
node = node.WithSet(WeaveTrustedSubnets, report.MakeStringSet(w.statusCache.Router.TrustedSubnets...))
if w.statusCache.IPAM != nil {
latests[WeaveIPAMStatus] = getIPAMStatus(*w.statusCache.IPAM)
latests[WeaveIPAMRange] = w.statusCache.IPAM.Range
latests[WeaveIPAMDefaultSubnet] = w.statusCache.IPAM.DefaultSubnet
}
if w.statusCache.DNS != nil {
latests[WeaveDNSDomain] = w.statusCache.DNS.Domain
latests[WeaveDNSUpstream] = strings.Join(w.statusCache.DNS.Upstream, ", ")
latests[WeaveDNSTTL] = fmt.Sprintf("%d", w.statusCache.DNS.TTL)
dnsEntryCount := 0
for _, entry := range w.statusCache.DNS.Entries {
if entry.Tombstone == 0 {
dnsEntryCount++
}
}
latests[WeaveDNSEntryCount] = fmt.Sprintf("%d", dnsEntryCount)
}
latests[WeaveProxyStatus] = "not running"
if w.statusCache.Proxy != nil {
latests[WeaveProxyStatus] = "running"
latests[WeaveProxyAddress] = ""
if len(w.statusCache.Proxy.Addresses) > 0 {
latests[WeaveProxyAddress] = w.statusCache.Proxy.Addresses[0]
}
}
latests[WeavePluginStatus] = "not running"
if w.statusCache.Plugin != nil {
latests[WeavePluginStatus] = "running"
latests[WeavePluginDriver] = w.statusCache.Plugin.DriverName
}
node = node.AddPrefixMulticolumnTable(WeaveConnectionsMulticolumnTablePrefix, getConnectionsTable(w.statusCache.Router))
node = node.WithParent(report.Host, w.hostID)
return latests, node
}
func getConnectionsTable(router weave.Router) []report.Row {
const (
outboundArrow = "->"
inboundArrow = "<-"
)
table := make([]report.Row, len(router.Connections))
for _, conn := range router.Connections {
arrow := inboundArrow
if conn.Outbound {
arrow = outboundArrow
}
table = append(table, report.Row{
ID: conn.Address,
Entries: map[string]string{
WeaveConnectionsConnection: fmt.Sprintf("%s %s", arrow, conn.Address),
WeaveConnectionsState: conn.State,
WeaveConnectionsInfo: conn.Info,
},
})
}
return table
}
func getIPAMStatus(ipam weave.IPAM) string {
allIPAMOwnersUnreachable := func(ipam weave.IPAM) bool {
for _, entry := range ipam.Entries {
if entry.Size > 0 && entry.IsKnownPeer {
return false
}
}
return true
}
if len(ipam.Entries) > 0 {
if allIPAMOwnersUnreachable(ipam) {
return "all ranges owned by unreachable peers"
} else if len(ipam.PendingAllocates) > 0 {
return "waiting for grant"
} else {
return "ready"
}
}
if ipam.Paxos != nil {
if ipam.Paxos.Elector {
return fmt.Sprintf(
"awaiting consensus (quorum: %d, known: %d)",
ipam.Paxos.Quorum,
ipam.Paxos.KnownNodes,
)
}
return "priming"
}
return "idle"
}