From d9b54ba60b6bd9b08ef73ad0917ffd3290f5f38e Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Mon, 1 Jun 2015 11:19:35 +0000 Subject: [PATCH] Yet More Plumbing for multi-host setup. - Move peers from flags to args in the app - Allow users to specify peers as IPs and hostname, both with and without ports - Allow users to specify peers on ./scope launch, and plumb that through entrypoint.sh and run-app - Improce ./scope usage text - Add brief document explaining how to cluster Scope --- app/main.go | 5 ++-- app/resolver.go | 37 +++++++++++++++++++++++------- app/resolver_test.go | 54 +++++++++++++++++++++++++++++--------------- docker/entrypoint.sh | 8 +++++++ docker/run-app | 2 +- docs/scope.md | 10 ++++++++ scope | 8 +++++-- 7 files changed, 92 insertions(+), 32 deletions(-) create mode 100644 docs/scope.md diff --git a/app/main.go b/app/main.go index 4c8bdf037..527931f37 100644 --- a/app/main.go +++ b/app/main.go @@ -10,7 +10,6 @@ import ( "os" "os/signal" "strconv" - "strings" "syscall" "time" @@ -24,12 +23,12 @@ func main() { var ( defaultProbes = []string{fmt.Sprintf("localhost:%d", xfer.ProbePort), fmt.Sprintf("scope.weave.local:%d", xfer.ProbePort)} logfile = flag.String("log", "stderr", "stderr, syslog, or filename") - probes = flag.String("probes", strings.Join(defaultProbes, ","), "list of probe endpoints, comma separated") batch = flag.Duration("batch", 1*time.Second, "batch interval") window = flag.Duration("window", 15*time.Second, "window") listen = flag.String("http.address", ":"+strconv.Itoa(xfer.AppPort), "webserver listen address") ) flag.Parse() + probes := append(defaultProbes, flag.Args()...) switch *logfile { case "stderr": @@ -62,7 +61,7 @@ func main() { c := xfer.NewCollector(*batch) defer c.Stop() - r := NewResolver(strings.Split(*probes, ","), c.AddAddress) + r := NewResolver(probes, c.AddAddress) defer r.Stop() lifo := NewReportLIFO(c, *window) diff --git a/app/resolver.go b/app/resolver.go index 036d68310..f36a92b24 100644 --- a/app/resolver.go +++ b/app/resolver.go @@ -3,7 +3,11 @@ package main import ( "log" "net" + "strconv" + "strings" "time" + + "github.com/weaveworks/scope/xfer" ) var ( @@ -42,11 +46,22 @@ func NewResolver(peers []string, add func(string)) Resolver { func prepareNames(strs []string) []peer { var results []peer for _, s := range strs { - hostname, port, err := net.SplitHostPort(s) - if err != nil { - log.Printf("invalid address %s: %v", s, err) - continue + var ( + hostname string + port string + ) + + if strings.Contains(s, ":") { + var err error + hostname, port, err = net.SplitHostPort(s) + if err != nil { + log.Printf("invalid address %s: %v", s, err) + continue + } + } else { + hostname, port = s, strconv.Itoa(xfer.ProbePort) } + results = append(results, peer{hostname, port}) } return results @@ -67,10 +82,16 @@ func (r Resolver) loop() { func (r Resolver) resolveHosts() { for _, peer := range r.peers { - addrs, err := lookupIP(peer.hostname) - if err != nil { - log.Printf("lookup %s: %v", peer.hostname, err) - continue + var addrs []net.IP + if addr := net.ParseIP(peer.hostname); addr != nil { + addrs = []net.IP{addr} + } else { + var err error + addrs, err = lookupIP(peer.hostname) + if err != nil { + log.Printf("lookup %s: %v", peer.hostname, err) + continue + } } for _, addr := range addrs { diff --git a/app/resolver_test.go b/app/resolver_test.go index 5c41d7d48..a40d248ec 100644 --- a/app/resolver_test.go +++ b/app/resolver_test.go @@ -1,10 +1,13 @@ package main import ( + "fmt" "net" "runtime" "testing" "time" + + "github.com/weaveworks/scope/xfer" ) func TestResolver(t *testing.T) { @@ -15,20 +18,21 @@ func TestResolver(t *testing.T) { oldLookupIP := lookupIP defer func() { lookupIP = oldLookupIP }() - ips := []net.IP{} - lookupIP = func(host string) ([]net.IP, error) { return ips, nil } + ips := map[string][]net.IP{} + lookupIP = func(host string) ([]net.IP, error) { + addrs, ok := ips[host] + if !ok { + return nil, fmt.Errorf("Not found") + } + return addrs, nil + } port := ":80" + ip1 := "192.168.0.1" + ip2 := "192.168.0.10" adds := make(chan string) add := func(s string) { adds <- s } - r := NewResolver([]string{"symbolic.name" + port}, add) - - c <- time.Now() // trigger initial resolve, with no endpoints - select { - case <-time.After(time.Millisecond): - case s := <-adds: - t.Errorf("got unexpected add: %q", s) - } + r := NewResolver([]string{"symbolic.name" + port, "namewithnoport", ip1 + port, ip2}, add) assertAdd := func(want string) { select { @@ -42,16 +46,30 @@ func TestResolver(t *testing.T) { } } - ip1 := "1.2.3.4" - ips = makeIPs(ip1) - c <- time.Now() // trigger a resolve - assertAdd(ip1 + port) // we want 1 add + // Initial resolve should just give us IPs + assertAdd(ip1 + port) + assertAdd(fmt.Sprintf("%s:%d", ip2, xfer.ProbePort)) - ip2 := "10.10.10.10" - ips = makeIPs(ip1, ip2) + // Trigger another resolve with a tick; again, + // just want ips. + c <- time.Now() + assertAdd(ip1 + port) + assertAdd(fmt.Sprintf("%s:%d", ip2, xfer.ProbePort)) + + ip3 := "1.2.3.4" + ips = map[string][]net.IP{"symbolic.name": makeIPs(ip3)} + c <- time.Now() // trigger a resolve + assertAdd(ip3 + port) // we want 1 add + assertAdd(ip1 + port) + assertAdd(fmt.Sprintf("%s:%d", ip2, xfer.ProbePort)) + + ip4 := "10.10.10.10" + ips = map[string][]net.IP{"symbolic.name": makeIPs(ip3, ip4)} c <- time.Now() // trigger another resolve, this time with 2 adds - assertAdd(ip1 + port) // first add - assertAdd(ip2 + port) // second add + assertAdd(ip3 + port) // first add + assertAdd(ip4 + port) // second add + assertAdd(ip1 + port) + assertAdd(fmt.Sprintf("%s:%d", ip2, xfer.ProbePort)) done := make(chan struct{}) go func() { r.Stop(); close(done) }() diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 100f00948..788a4a163 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -31,4 +31,12 @@ if [ -n "$DNS_SERVER" -a -n "$SEARCHPATH" ]; then echo "nameserver $DNS_SERVER" >>/etc/resolv.conf fi +# End of the command line can optionally be some +# addresses of probes to connect to, for people not +# using Weave DNS. We stick these in /etc/weave/probes +# for the run-app script to pick up. +MANUAL_PROBES=$@ +mkdir -p /etc/weave +echo "$MANUAL_PROBES" >/etc/weave/probes + exec /sbin/runsvdir /etc/service diff --git a/docker/run-app b/docker/run-app index 8e7be9331..df910e5f5 100755 --- a/docker/run-app +++ b/docker/run-app @@ -1,3 +1,3 @@ #!/bin/sh -exec /home/weave/app +exec /home/weave/app $(cat /etc/weave/probes) diff --git a/docs/scope.md b/docs/scope.md new file mode 100644 index 000000000..d0782bebc --- /dev/null +++ b/docs/scope.md @@ -0,0 +1,10 @@ + +## Multi host setup + +Weave Scope uses WeaveDNS to automatically discover other instances of Scope running on your network. If you have a running WeaveDNS setup, you do not need any further steps. + +If you do not wish to use WeaveDNS, you can instruct Scope to cluster with other Scope instances on the command line. Hostnames and IP addresses are acceptable, both with and without ports: + +``` +# weave launch scope1:4030 192.168.0.12 192.168.0.11:4030 +``` diff --git a/scope b/scope index 4333be4c9..f60771abb 100755 --- a/scope +++ b/scope @@ -1,7 +1,12 @@ #!/bin/bash usage() { - echo "$0 (launch|stop)" + echo "Usage:" + echo "scope launch [ ...]" + echo "scope stop" + echo + echo "scope is of the form [:]" + exit 1 } SCOPE_IMAGE=weaveworks/scope @@ -118,7 +123,6 @@ container_ip() { case "$COMMAND" in launch) - [ $# -eq 0 ] || usage check_not_running $CONTAINER_NAME $IMAGE # If WeaveDNS is running, we want to automatically tell the scope