Files
weave-scope/probe/overlay/weave.go
Bryan Boreham ade54ba84e probe: stop calling 'weave ps'
Now that we enter the container namespace to fetch IPs for every
container, there is no need to have 'weave ps' do it.

This does mean we lose Weave MAC addresses, but that is a rather
idiosyncratic feature anyway.
2018-06-02 22:22:08 +00:00

395 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/probe/host"
"github.com/weaveworks/scope/report"
)
// Keys for use in Node
const (
WeavePeerName = "weave_peer_name"
WeavePeerNickName = "weave_peer_nick_name"
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(host.LocalNetworks, 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.WithParents(report.MakeSets().Add(report.Host, report.MakeStringSet(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"
}