Merge pull request #274 from tomwilkie/probe-host

Move probe host topology code into its own module
This commit is contained in:
Tom Wilkie
2015-06-23 15:55:42 +02:00
11 changed files with 250 additions and 51 deletions

View File

@@ -23,7 +23,7 @@ $(SCOPE_EXPORT): $(APP_EXE) $(PROBE_EXE) docker/*
$(APP_EXE): app/*.go render/*.go report/*.go xfer/*.go
$(PROBE_EXE): probe/*.go probe/tag/*.go probe/docker/*.go probe/endpoint/*.go probe/process/*.go report/*.go xfer/*.go
$(PROBE_EXE): probe/*.go probe/docker/*.go probe/endpoint/*.go probe/host/*.go probe/process/*.go probe/tag/*.go report/*.go xfer/*.go
$(APP_EXE) $(PROBE_EXE):
go get -tags netgo ./$(@D)

View File

@@ -6,6 +6,7 @@ import (
"github.com/gorilla/mux"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
)
@@ -20,16 +21,16 @@ type OriginHost struct {
}
func getOriginHost(t report.Topology, nodeID string) (OriginHost, bool) {
host, ok := t.NodeMetadatas[nodeID]
h, ok := t.NodeMetadatas[nodeID]
if !ok {
return OriginHost{}, false
}
return OriginHost{
Hostname: host["host_name"],
OS: host["os"],
Networks: strings.Split(host["local_networks"], " "),
Load: host["load"],
Hostname: h[host.HostName],
OS: h[host.OS],
Networks: strings.Split(h[host.LocalNetworks], " "),
Load: h[host.Load],
}, true
}

128
probe/host/reporter.go Normal file
View File

@@ -0,0 +1,128 @@
package host
import (
"fmt"
"io/ioutil"
"net"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"github.com/weaveworks/scope/probe/tag"
"github.com/weaveworks/scope/report"
)
// Keys for use in NodeMetadata
const (
Timestamp = "ts"
HostName = "host_name"
LocalNetworks = "local_networks"
OS = "os"
Load = "load"
KernelVersion = "kernel_version"
Uptime = "uptime"
)
// Exposed for testing
const (
ProcUptime = "/proc/uptime"
ProcLoad = "/proc/loadavg"
)
// Exposed for testing
var (
InterfaceAddrs = net.InterfaceAddrs
Now = func() string { return time.Now().UTC().Format(time.RFC3339Nano) }
ReadFile = ioutil.ReadFile
Uname = syscall.Uname
)
type reporter struct {
hostID string
hostName string
}
// NewReporter returns a Reporter which produces a report containing host
// topology for this host.
func NewReporter(hostID, hostName string) tag.Reporter {
return &reporter{
hostID: hostID,
hostName: hostName,
}
}
func getUptime() (time.Duration, error) {
var result time.Duration
buf, err := ReadFile(ProcUptime)
if err != nil {
return result, err
}
fields := strings.Fields(string(buf))
if len(fields) != 2 {
return result, fmt.Errorf("invalid format: %s", string(buf))
}
uptime, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return result, err
}
return time.Duration(uptime) * time.Second, nil
}
func charsToString(ca [65]int8) string {
s := make([]byte, len(ca))
var lens int
for ; lens < len(ca); lens++ {
if ca[lens] == 0 {
break
}
s[lens] = uint8(ca[lens])
}
return string(s[0:lens])
}
func (r *reporter) Report() (report.Report, error) {
var (
rep = report.MakeReport()
localCIDRs []string
utsname syscall.Utsname
)
localNets, err := InterfaceAddrs()
if err != nil {
return rep, err
}
for _, localNet := range localNets {
// Not all networks are IP networks.
if ipNet, ok := localNet.(*net.IPNet); ok {
localCIDRs = append(localCIDRs, ipNet.String())
}
}
if err := Uname(&utsname); err != nil {
return rep, err
}
kernel := fmt.Sprintf("%s %s", charsToString(utsname.Release), charsToString(utsname.Version))
uptime, err := getUptime()
if err != nil {
return rep, err
}
rep.Host.NodeMetadatas[report.MakeHostNodeID(r.hostID)] = report.NodeMetadata{
Timestamp: Now(),
HostName: r.hostName,
LocalNetworks: strings.Join(localCIDRs, " "),
OS: runtime.GOOS,
Load: getLoad(),
KernelVersion: kernel,
Uptime: uptime.String(),
}
return rep, nil
}

View File

@@ -0,0 +1,84 @@
package host_test
import (
"net"
"reflect"
"runtime"
"syscall"
"testing"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
)
const (
procLoad = "0.59 0.36 0.29 1/200 12187"
procUptime = "1004143.23 1263220.30"
release = "release"
version = "version"
network = "192.168.0.0/16"
hostid = "hostid"
now = "now"
hostname = "hostname"
load = "0.59 0.36 0.29"
uptime = "278h55m43s"
kernel = "release version"
)
func string2c(s string) [65]int8 {
var result [65]int8
for i, c := range s {
result[i] = int8(c)
}
return result
}
func TestReporter(t *testing.T) {
oldInterfaceAddrs, oldNow, oldReadFile, oldUname := host.InterfaceAddrs, host.Now, host.ReadFile, host.Uname
defer func() {
host.InterfaceAddrs, host.Now, host.ReadFile, host.Uname = oldInterfaceAddrs, oldNow, oldReadFile, oldUname
}()
host.InterfaceAddrs = func() ([]net.Addr, error) {
_, ipnet, _ := net.ParseCIDR(network)
return []net.Addr{ipnet}, nil
}
host.Now = func() string { return now }
host.ReadFile = func(filename string) ([]byte, error) {
switch filename {
case host.ProcUptime:
return []byte(procUptime), nil
case host.ProcLoad:
return []byte(procLoad), nil
default:
panic(filename)
}
}
host.Uname = func(uts *syscall.Utsname) error {
uts.Release = string2c(release)
uts.Version = string2c(version)
return nil
}
r := host.NewReporter(hostid, hostname)
have, _ := r.Report()
want := report.MakeReport()
want.Host.NodeMetadatas[report.MakeHostNodeID(hostid)] = report.NodeMetadata{
host.Timestamp: now,
host.HostName: hostname,
host.LocalNetworks: network,
host.OS: runtime.GOOS,
host.Load: load,
host.Uptime: uptime,
host.KernelVersion: kernel,
}
if !reflect.DeepEqual(want, have) {
t.Errorf("%s", test.Diff(want, have))
}
}

View File

@@ -1,4 +1,4 @@
package main
package host
import (
"fmt"

View File

@@ -1,14 +1,13 @@
package main
package host
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
)
func getLoad() string {
buf, err := ioutil.ReadFile("/proc/loadavg")
buf, err := ReadFile(ProcLoad)
if err != nil {
return "unknown"
}

View File

@@ -3,19 +3,18 @@ package main
import (
"flag"
"log"
"net"
"net/http"
"os"
"os/signal"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"github.com/weaveworks/procspy"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/endpoint"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/probe/tag"
"github.com/weaveworks/scope/report"
@@ -80,8 +79,15 @@ func main() {
weaveTagger *tag.WeaveTagger
)
taggers := []tag.Tagger{tag.NewTopologyTagger(), tag.NewOriginHostTagger(hostID)}
reporters := []tag.Reporter{endpoint.NewReporter(hostID, hostName, *spyProcs)}
taggers := []tag.Tagger{
tag.NewTopologyTagger(),
tag.NewOriginHostTagger(hostID),
}
reporters := []tag.Reporter{
host.NewReporter(hostID, hostName),
endpoint.NewReporter(hostID, hostName, *spyProcs),
}
if *dockerEnabled && runtime.GOOS == linux {
if err = report.AddLocalBridge(*dockerBridge); err != nil {
@@ -131,9 +137,6 @@ func main() {
r = report.MakeReport()
case <-spyTick:
// Do this every tick so it gets tagged by the OriginHostTagger
r.Host = hostTopology(hostID, hostName)
for _, reporter := range reporters {
newReport, err := reporter.Report()
if err != nil {
@@ -157,29 +160,6 @@ func main() {
log.Printf("%s", <-interrupt())
}
// hostTopology produces a host topology for this host. No need to do this
// more than once per published report.
func hostTopology(hostID, hostName string) report.Topology {
var localCIDRs []string
if localNets, err := net.InterfaceAddrs(); err == nil {
// Not all networks are IP networks.
for _, localNet := range localNets {
if ipNet, ok := localNet.(*net.IPNet); ok {
localCIDRs = append(localCIDRs, ipNet.String())
}
}
}
t := report.NewTopology()
t.NodeMetadatas[report.MakeHostNodeID(hostID)] = report.NodeMetadata{
"ts": time.Now().UTC().Format(time.RFC3339Nano),
"host_name": hostName,
"local_networks": strings.Join(localCIDRs, " "),
"os": runtime.GOOS,
"load": getLoad(),
}
return t
}
func interrupt() chan os.Signal {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)

View File

@@ -6,6 +6,7 @@ import (
"strconv"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
)
@@ -208,15 +209,18 @@ func containerImageOriginTable(nmd report.NodeMetadata) (Table, bool) {
func hostOriginTable(nmd report.NodeMetadata) (Table, bool) {
rows := []Row{}
if val, ok := nmd["host_name"]; ok {
rows = append(rows, Row{"Host name", val, ""})
}
if val, ok := nmd["load"]; ok {
rows = append(rows, Row{"Load", val, ""})
}
if val, ok := nmd["os"]; ok {
rows = append(rows, Row{"Operating system", val, ""})
for _, tuple := range []struct{ key, human string }{
{host.HostName, "Host name"},
{host.Load, "Load"},
{host.OS, "Operating system"},
{host.KernelVersion, "Kernel version"},
{host.Uptime, "Uptime"},
} {
if val, ok := nmd[tuple.key]; ok {
rows = append(rows, Row{Key: tuple.human, ValueMajor: val, ValueMinor: ""})
}
}
return Table{
Title: "Origin Host",
Numeric: false,

View File

@@ -6,6 +6,7 @@ import (
"strings"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
)
@@ -123,7 +124,7 @@ func MapAddressIdentity(m report.NodeMetadata) (RenderableNode, bool) {
func MapHostIdentity(m report.NodeMetadata) (RenderableNode, bool) {
var (
id = MakeHostID(report.ExtractHostID(m))
hostname = m["host_name"]
hostname = m[host.HostName]
parts = strings.SplitN(hostname, ".", 2)
major, minor, rank = "", "", ""
)

View File

@@ -4,6 +4,7 @@ import (
"net"
"strings"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
)
@@ -18,7 +19,7 @@ func LocalNetworks(r report.Report) report.Networks {
)
for _, md := range r.Host.NodeMetadatas {
val, ok := md["local_networks"]
val, ok := md[host.LocalNetworks]
if !ok {
continue
}

View File

@@ -5,6 +5,7 @@ import (
"reflect"
"testing"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/render"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
@@ -14,7 +15,7 @@ func TestReportLocalNetworks(t *testing.T) {
r := report.MakeReport()
r.Merge(report.Report{Host: report.Topology{NodeMetadatas: report.NodeMetadatas{
"nonets": {},
"foo": {"local_networks": "10.0.0.1/8 192.168.1.1/24 10.0.0.1/8 badnet/33"},
"foo": {host.LocalNetworks: "10.0.0.1/8 192.168.1.1/24 10.0.0.1/8 badnet/33"},
}}})
want := report.Networks([]*net.IPNet{
mustParseCIDR("10.0.0.1/8"),