From fc9a1c8664fcecff697d1ac392e726a2957d8c09 Mon Sep 17 00:00:00 2001 From: Adam Harrison Date: Thu, 16 Jul 2015 13:41:09 +0100 Subject: [PATCH] new `weave status`, `weave status ...`, and `weave report` --- common/signals.go | 5 - common/utils.go | 12 ++ ipam/allocate.go | 4 - ipam/allocator.go | 35 ----- ipam/claim.go | 4 - ipam/paxos/paxos.go | 15 +- ipam/status.go | 82 +++++++++++ nameserver/nameserver.go | 17 --- nameserver/status.go | 43 ++++++ prog/weaver/http.go | 194 ++++++++++++++++++++++--- prog/weaver/main.go | 41 ++---- router/connection.go | 12 -- router/connection_maker.go | 53 ------- router/http.go | 12 +- router/json.go | 116 --------------- router/mac_cache.go | 12 -- router/peers.go | 29 ---- router/router.go | 20 --- router/routes.go | 19 --- router/status.go | 215 +++++++++++++++++++++++++++ site/features.md | 5 + site/ipam.md | 30 ++-- site/troubleshooting.md | 224 +++++++++++++++++++---------- site/weavedns.md | 20 +-- test/150_connect_forget_2_test.sh | 3 +- test/700_status_and_report_test.sh | 29 ++++ weave | 26 +++- 27 files changed, 784 insertions(+), 493 deletions(-) create mode 100644 ipam/status.go create mode 100644 nameserver/status.go delete mode 100644 router/json.go create mode 100644 router/status.go create mode 100755 test/700_status_and_report_test.sh diff --git a/common/signals.go b/common/signals.go index 48ed747c..f5eedf0a 100644 --- a/common/signals.go +++ b/common/signals.go @@ -10,7 +10,6 @@ import ( // A subsystem/server/... that can be stopped or queried about the status with a signal type SignalReceiver interface { Stop() error - Status() string } func SignalHandlerLoop(ss ...SignalReceiver) { @@ -28,10 +27,6 @@ func SignalHandlerLoop(ss ...SignalReceiver) { case syscall.SIGQUIT: stacklen := runtime.Stack(buf, true) Log.Infof("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end", buf[:stacklen]) - case syscall.SIGUSR1: - for _, subsystem := range ss { - Log.Infof("=== received SIGUSR1 ===\n*** status...\n%s\n*** end", subsystem.Status()) - } } } } diff --git a/common/utils.go b/common/utils.go index 0ac2ca08..82959321 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,8 +1,20 @@ package common +import ( + "strings" +) + // Assert test is true, panic otherwise func Assert(test bool) { if !test { panic("Assertion failure") } } + +func ErrorMessages(errors []error) string { + var result []string + for _, err := range errors { + result = append(result, err.Error()) + } + return strings.Join(result, "\n") +} diff --git a/ipam/allocate.go b/ipam/allocate.go index d4a07964..ebb30217 100644 --- a/ipam/allocate.go +++ b/ipam/allocate.go @@ -62,10 +62,6 @@ func (g *allocate) Cancel() { g.resultChan <- allocateResult{0, fmt.Errorf("Allocate request for %s cancelled", g.ident)} } -func (g *allocate) String() string { - return fmt.Sprintf("Allocate for %s", g.ident) -} - func (g *allocate) ForContainer(ident string) bool { return g.ident == ident } diff --git a/ipam/allocator.go b/ipam/allocator.go index e8f8dc13..28f2e449 100644 --- a/ipam/allocator.go +++ b/ipam/allocator.go @@ -33,8 +33,6 @@ type operation interface { Cancel() - String() string - // Does this operation pertain to the given container id? // Used for tidying up pending operations when containers die. ForContainer(ident string) bool @@ -275,15 +273,6 @@ func (alloc *Allocator) Free(ident string, addrToFree address.Address) error { return <-errChan } -// Sync. -func (alloc *Allocator) String() string { - resultChan := make(chan string) - alloc.actionChan <- func() { - resultChan <- alloc.string() - } - return <-resultChan -} - // Shutdown (Sync) func (alloc *Allocator) Shutdown() { alloc.infof("Shutdown") @@ -493,30 +482,6 @@ func (alloc *Allocator) actorLoop(actionChan <-chan func()) { // Helper functions -func (alloc *Allocator) string() string { - var buf bytes.Buffer - fmt.Fprintf(&buf, "Allocator range %s", alloc.universe) - - if alloc.ring.Empty() { - if alloc.paxosTicker != nil { - fmt.Fprintf(&buf, " awaiting consensus: %s", alloc.paxos.String()) - } - } else { - fmt.Fprint(&buf, "\nOwned Ranges:") - alloc.ring.FprintWithNicknames(&buf, alloc.nicknames) - } - if len(alloc.pendingAllocates)+len(alloc.pendingClaims) > 0 { - fmt.Fprintf(&buf, "\nPending requests:") - for _, op := range alloc.pendingAllocates { - fmt.Fprintf(&buf, "\n %s", op.String()) - } - for _, op := range alloc.pendingClaims { - fmt.Fprintf(&buf, "\n %s", op.String()) - } - } - return buf.String() -} - // Ensure we are making progress towards an established ring func (alloc *Allocator) establishRing() { if !alloc.ring.Empty() || alloc.paxosTicker != nil { diff --git a/ipam/claim.go b/ipam/claim.go index 9525b956..5e79d7d1 100644 --- a/ipam/claim.go +++ b/ipam/claim.go @@ -98,10 +98,6 @@ func (c *claim) Cancel() { c.sendResult(fmt.Errorf("Operation cancelled.")) } -func (c *claim) String() string { - return fmt.Sprintf("Claim %s -> %s", c.ident, c.addr.String()) -} - func (c *claim) ForContainer(ident string) bool { return c.ident == ident } diff --git a/ipam/paxos/paxos.go b/ipam/paxos/paxos.go index 61fda181..18d3aeea 100644 --- a/ipam/paxos/paxos.go +++ b/ipam/paxos/paxos.go @@ -1,8 +1,6 @@ package paxos import ( - "fmt" - "github.com/weaveworks/weave/router" ) @@ -252,9 +250,14 @@ func (node *Node) Consensus() (bool, AcceptedValue) { return false, AcceptedValue{} } -func (node *Node) String() string { - if ok, val := node.Consensus(); ok { - return fmt.Sprintf("Consensus reached with size %d", len(val.Value)) +type Status struct { + KnownNodes int + Quorum uint +} + +func NewStatus(node *Node) *Status { + if node == nil { + return nil } - return fmt.Sprintf("Nodes known: %d, Quorum size: %d", len(node.knows), node.quorum) + return &Status{len(node.knows), node.quorum} } diff --git a/ipam/status.go b/ipam/status.go new file mode 100644 index 00000000..72df8d3e --- /dev/null +++ b/ipam/status.go @@ -0,0 +1,82 @@ +package ipam + +import ( + "github.com/weaveworks/weave/ipam/paxos" + "github.com/weaveworks/weave/net/address" +) + +type Status struct { + Paxos *paxos.Status + Range string + DefaultSubnet string + Entries []EntryStatus + PendingClaims []ClaimStatus + PendingAllocates []string +} + +type EntryStatus struct { + Token string + Peer string + Version uint32 +} + +type ClaimStatus struct { + Ident string + Address address.Address +} + +func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status { + if allocator == nil { + return nil + } + + var paxosStatus *paxos.Status + if allocator.paxosTicker != nil { + paxosStatus = paxos.NewStatus(allocator.paxos) + } + + resultChan := make(chan *Status) + allocator.actionChan <- func() { + resultChan <- &Status{ + paxosStatus, + allocator.universe.String(), + defaultSubnet.String(), + newEntryStatusSlice(allocator), + newClaimStatusSlice(allocator), + newAllocateIdentSlice(allocator)} + } + + return <-resultChan +} + +func newEntryStatusSlice(allocator *Allocator) []EntryStatus { + var slice []EntryStatus + + if allocator.ring.Empty() { + return slice + } + + for _, entry := range allocator.ring.Entries { + slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.Version}) + } + + return slice +} + +func newClaimStatusSlice(allocator *Allocator) []ClaimStatus { + var slice []ClaimStatus + for _, op := range allocator.pendingClaims { + claim := op.(*claim) + slice = append(slice, ClaimStatus{claim.ident, claim.addr}) + } + return slice +} + +func newAllocateIdentSlice(allocator *Allocator) []string { + var slice []string + for _, op := range allocator.pendingAllocates { + allocate := op.(*allocate) + slice = append(slice, allocate.ident) + } + return slice +} diff --git a/nameserver/nameserver.go b/nameserver/nameserver.go index b3a0975c..1827df95 100644 --- a/nameserver/nameserver.go +++ b/nameserver/nameserver.go @@ -185,23 +185,6 @@ func (n *Nameserver) deleteTombstones() { }) } -func (n *Nameserver) String() string { - n.RLock() - defer n.RUnlock() - var buf bytes.Buffer - for _, entry := range n.entries { - if entry.Tombstone > 0 { - continue - } - containerid := entry.ContainerID - if len(containerid) > 12 { - containerid = containerid[:12] - } - fmt.Fprintf(&buf, "%s: %s [%s]\n", containerid, entry.Hostname, entry.Addr.String()) - } - return buf.String() -} - func (n *Nameserver) Gossip() router.GossipData { n.RLock() defer n.RUnlock() diff --git a/nameserver/status.go b/nameserver/status.go new file mode 100644 index 00000000..6124f08d --- /dev/null +++ b/nameserver/status.go @@ -0,0 +1,43 @@ +package nameserver + +type Status struct { + Domain string + Address string + TTL uint32 + Entries []EntryStatus +} + +type EntryStatus struct { + Hostname string + Origin string + ContainerID string + Address string + Version int + Tombstone int64 +} + +func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status { + if dnsServer == nil { + return nil + } + + ns.RLock() + defer ns.RUnlock() + + var entryStatusSlice []EntryStatus + for _, entry := range ns.entries { + entryStatusSlice = append(entryStatusSlice, EntryStatus{ + entry.Hostname, + entry.Origin.String(), + entry.ContainerID, + entry.Addr.String(), + entry.Version, + entry.Tombstone}) + } + + return &Status{ + dnsServer.domain, + dnsServer.address, + dnsServer.ttl, + entryStatusSlice} +} diff --git a/prog/weaver/http.go b/prog/weaver/http.go index e26a27c9..7f842521 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -1,44 +1,198 @@ package main import ( + "encoding/json" "fmt" "github.com/gorilla/mux" + . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/ipam" "github.com/weaveworks/weave/nameserver" "github.com/weaveworks/weave/net/address" weave "github.com/weaveworks/weave/router" "net/http" + "strings" + "text/template" ) -func HandleHTTP(muxRouter *mux.Router, - router *weave.Router, - allocator *ipam.Allocator, - defaultSubnet address.CIDR, - ns *nameserver.Nameserver, - dnsserver *nameserver.DNSServer) { +var rootTemplate = template.New("root").Funcs(map[string]interface{}{ + "countDNSEntries": func(entries []nameserver.EntryStatus) int { + count := 0 + for _, entry := range entries { + if entry.Tombstone == 0 { + count++ + } + } + return count + }, + "printConnectionCounts": func(conns []weave.LocalConnectionStatus) string { + counts := make(map[string]int) + for _, conn := range conns { + counts[conn.State]++ + } + return printCounts(counts, []string{"established", "pending", "retrying", "failed", "connecting"}) + }, + "printPeerConnectionCounts": func(peers []weave.PeerStatus) string { + counts := make(map[string]int) + for _, peer := range peers { + for _, conn := range peer.Connections { + if conn.Established { + counts["established"]++ + } else { + counts["pending"]++ + } + } + } + return printCounts(counts, []string{"established", "pending"}) + }, + "printState": func(enabled bool) string { + if enabled { + return "enabled" + } + return "disabled" + }, + "trimSuffix": strings.TrimSuffix, +}) - muxRouter.Methods("GET").Path("/status").Headers("Accept", "application/json").HandlerFunc( +// Print counts in a specified order +func printCounts(counts map[string]int, keys []string) string { + var stringCounts []string + for _, key := range keys { + if count, ok := counts[key]; ok { + stringCounts = append(stringCounts, fmt.Sprintf("%d %s", count, key)) + } + } + return strings.Join(stringCounts, ", ") +} + +// Strip escaped newlines from template +func escape(template string) string { + return strings.Replace(template, "\\\n", "", -1) +} + +// Define a named template panicking on error +func defTemplate(name string, text string) *template.Template { + return template.Must(rootTemplate.New(name).Parse(escape(text))) +} + +var statusTemplate = defTemplate("status", `\ + Version: {{.Version}} + + Service: router + Protocol: {{.Router.Protocol}} \ +{{if eq .Router.ProtocolMinVersion .Router.ProtocolMaxVersion}}\ +{{.Router.ProtocolMaxVersion}}\ +{{else}}\ +{{.Router.ProtocolMinVersion}}..{{.Router.ProtocolMaxVersion}}\ +{{end}} + Name: {{.Router.Name}}({{.Router.NickName}}) + Encryption: {{printState .Router.Encryption}} + PeerDiscovery: {{printState .Router.PeerDiscovery}} + Targets: {{len .Router.Targets}} + Connections: {{len .Router.Connections}}{{with printConnectionCounts .Router.Connections}} ({{.}}){{end}} + Peers: {{len .Router.Peers}}{{with printPeerConnectionCounts .Router.Peers}} (with {{.}} connections between them){{end}} +{{if .IPAM}}\ + + Service: ipam +{{if .IPAM.Entries}}\ + Consensus: achieved +{{else if .IPAM.Paxos}}\ + Consensus: waiting (quorum: {{.IPAM.Paxos.Quorum}}, known: {{.IPAM.Paxos.KnownNodes}}) +{{else}}\ + Consensus: deferred +{{end}}\ + Range: {{.IPAM.Range}} + DefaultSubnet: {{.IPAM.DefaultSubnet}} +{{end}}\ +{{if .DNS}}\ + + Service: dns + Domain: {{.DNS.Domain}} + TTL: {{.DNS.TTL}} + Entries: {{countDNSEntries .DNS.Entries}} +{{end}}\ +`) + +var targetsTemplate = defTemplate("targetsTemplate", `\ +{{range .Router.Targets}}{{.}} +{{end}}\ +`) + +var connectionsTemplate = defTemplate("connectionsTemplate", `\ +{{range .Router.Connections}}\ +{{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} {{printf "%-11v" .State}} {{.Info}} +{{end}}\ +`) + +var peersTemplate = defTemplate("peers", `\ +{{range .Router.Peers}}\ +{{.Name}}({{.NickName}}) +{{range .Connections}}\ + {{if .Outbound}}->{{else}}<-{{end}} {{printf "%-21v" .Address}} \ +{{$nameNickName := printf "%v(%v)" .Name .NickName}}{{printf "%-32v" $nameNickName}} \ +{{if .Established}}established{{else}}pending{{end}} +{{end}}\ +{{end}}\ +`) + +var dnsEntriesTemplate = defTemplate("dnsEntries", `\ +{{$domain := printf ".%v" .DNS.Domain}}\ +{{range .DNS.Entries}}\ +{{if eq .Tombstone 0}}\ +{{$hostname := trimSuffix .Hostname $domain}}\ +{{printf "%-12v" $hostname}} {{printf "%-15v" .Address}} {{printf "%12.12v" .ContainerID}} {{.Origin}} +{{end}}\ +{{end}}\ +`) + +type WeaveStatus struct { + Version string + Router *weave.Status `json:"Router,omitempty"` + IPAM *ipam.Status `json:"IPAM,omitempty"` + DNS *nameserver.Status `json:"DNS,omitempty"` +} + +func HandleHTTP(muxRouter *mux.Router, version string, router *weave.Router, allocator *ipam.Allocator, defaultSubnet address.CIDR, ns *nameserver.Nameserver, dnsserver *nameserver.DNSServer) { + status := func() WeaveStatus { + return WeaveStatus{ + version, + weave.NewStatus(router), + ipam.NewStatus(allocator, defaultSubnet), + nameserver.NewStatus(ns, dnsserver)} + } + muxRouter.Methods("GET").Path("/report").Headers("Accept", "application/json").HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - json, _ := router.StatusJSON(version) + json, _ := json.MarshalIndent(status(), "", " ") w.Header().Set("Content-Type", "application/json") w.Write(json) }) - muxRouter.Methods("GET").Path("/status").HandlerFunc( + muxRouter.Methods("GET").Path("/report").Queries("format", "{format}").HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "weave router", version) - fmt.Fprintln(w, router.Status()) - if allocator != nil { - fmt.Fprintln(w, allocator.String()) - fmt.Fprintln(w, "Allocator default subnet:", defaultSubnet) + formatTemplate, err := template.New("format").Parse(mux.Vars(r)["format"]) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return } - fmt.Fprintln(w, "") - if dnsserver == nil { - fmt.Fprintln(w, "WeaveDNS is disabled") - } else { - fmt.Fprintln(w, dnsserver.String()) - fmt.Fprintln(w, ns.String()) + if err := formatTemplate.Execute(w, status()); err != nil { + http.Error(w, "error during template execution", http.StatusInternalServerError) + Log.Error(err) } }) + defHandler := func(path string, template *template.Template) { + muxRouter.Methods("GET").Path(path).HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if err := template.Execute(w, status()); err != nil { + http.Error(w, "error during template execution", http.StatusInternalServerError) + Log.Error(err) + } + }) + } + + defHandler("/status", statusTemplate) + defHandler("/status/targets", targetsTemplate) + defHandler("/status/connections", connectionsTemplate) + defHandler("/status/peers", peersTemplate) + defHandler("/status/dns", dnsEntriesTemplate) + } diff --git a/prog/weaver/main.go b/prog/weaver/main.go index d04cb017..4344d774 100644 --- a/prog/weaver/main.go +++ b/prog/weaver/main.go @@ -193,27 +193,29 @@ func main() { router.Start() if errors := router.ConnectionMaker.InitiateConnections(peers, false); len(errors) > 0 { - Log.Fatal(errorMessages(errors)) + Log.Fatal(ErrorMessages(errors)) } // The weave script always waits for a status call to succeed, // so there is no point in doing "weave launch --http-addr ''". // This is here to support stand-alone use of weaver. if httpAddr != "" { - go handleHTTP(router, httpAddr, allocator, defaultSubnet, dockerCli, ns, dnsserver) + muxRouter := mux.NewRouter() + if allocator != nil { + allocator.HandleHTTP(muxRouter, defaultSubnet, dockerCli) + } + if ns != nil { + ns.HandleHTTP(muxRouter) + } + router.HandleHTTP(muxRouter) + HandleHTTP(muxRouter, version, router, allocator, defaultSubnet, ns, dnsserver) + http.Handle("/", muxRouter) + go listenAndServeHTTP(httpAddr, muxRouter) } SignalHandlerLoop(router) } -func errorMessages(errors []error) string { - var result []string - for _, err := range errors { - result = append(result, err.Error()) - } - return strings.Join(result, "\n") -} - func options() map[string]string { options := make(map[string]string) mflag.Visit(func(f *mflag.Flag) { @@ -304,23 +306,7 @@ func determineQuorum(initPeerCountFlag int, peers []string) uint { return quorum } -func handleHTTP(router *weave.Router, httpAddr string, allocator *ipam.Allocator, defaultSubnet address.CIDR, docker *docker.Client, ns *nameserver.Nameserver, dnsserver *nameserver.DNSServer) { - muxRouter := mux.NewRouter() - - if allocator != nil { - allocator.HandleHTTP(muxRouter, defaultSubnet, docker) - } - - if ns != nil { - ns.HandleHTTP(muxRouter) - } - - router.HandleHTTP(muxRouter) - - HandleHTTP(muxRouter, router, allocator, defaultSubnet, ns, dnsserver) - - http.Handle("/", muxRouter) - +func listenAndServeHTTP(httpAddr string, muxRouter *mux.Router) { protocol := "tcp" if strings.HasPrefix(httpAddr, "/") { os.Remove(httpAddr) // in case it's there from last time @@ -330,7 +316,6 @@ func handleHTTP(router *weave.Router, httpAddr string, allocator *ipam.Allocator if err != nil { Log.Fatal("Unable to create http listener socket: ", err) } - err = http.Serve(l, nil) if err != nil { Log.Fatal("Unable to create http server", err) diff --git a/router/connection.go b/router/connection.go index 90bb327c..fedd0fe1 100644 --- a/router/connection.go +++ b/router/connection.go @@ -89,18 +89,6 @@ func (conn *RemoteConnection) ErrorLog(args ...interface{}) { log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...) } -func (conn *RemoteConnection) String() string { - from := "" - if conn.local != nil { - from = conn.local.String() - } - to := "" - if conn.remote != nil { - to = conn.remote.String() - } - return fmt.Sprint("Connection ", from, "->", to) -} - // Does not return anything. If the connection is successful, it will // end up in the local peer's connections map. func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router, acceptNewPeer bool) { diff --git a/router/connection_maker.go b/router/connection_maker.go index c9859bd2..9e8c2ebc 100644 --- a/router/connection_maker.go +++ b/router/connection_maker.go @@ -1,7 +1,6 @@ package router import ( - "bytes" "fmt" "math/rand" "net" @@ -104,58 +103,6 @@ func (cm *ConnectionMaker) Refresh() { cm.actionChan <- func() bool { return true } } -func (cm *ConnectionMaker) Status() ConnectionMakerStatus { - // We need to Refresh first in order to clear out any 'attempting' - // connections from cm.targets that have been established since - // the last run of cm.checkStateAndAttemptConnections. These - // entries are harmless but do represent stale state that we do - // not want to report. - cm.Refresh() - resultChan := make(chan ConnectionMakerStatus, 0) - cm.actionChan <- func() bool { - status := ConnectionMakerStatus{ - DirectPeers: []string{}, - Reconnects: make(map[string]Target), - } - for peer := range cm.directPeers { - status.DirectPeers = append(status.DirectPeers, peer) - } - - for address, target := range cm.targets { - status.Reconnects[address] = *target - } - resultChan <- status - return false - } - return <-resultChan -} - -type ConnectionMakerStatus struct { - DirectPeers []string - Reconnects map[string]Target -} - -func (status ConnectionMakerStatus) String() string { - var buf bytes.Buffer - fmt.Fprint(&buf, "Direct Peers:") - for _, peer := range status.DirectPeers { - fmt.Fprintf(&buf, " %s", peer) - } - fmt.Fprintln(&buf, "\nReconnects:") - for address, target := range status.Reconnects { - fmt.Fprintf(&buf, "->[%s]", address) - if target.lastError != nil { - fmt.Fprintf(&buf, " (%s)", target.lastError) - } - if target.attempting { - fmt.Fprintf(&buf, " trying since %v\n", target.tryAfter) - } else { - fmt.Fprintf(&buf, " next try at %v\n", target.tryAfter) - } - } - return buf.String() -} - func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) { timer := time.NewTimer(MaxDuration) run := func() { timer.Reset(cm.checkStateAndAttemptConnections()) } diff --git a/router/http.go b/router/http.go index b541f186..7ad552b8 100644 --- a/router/http.go +++ b/router/http.go @@ -3,18 +3,10 @@ package router import ( "fmt" "github.com/gorilla/mux" + "github.com/weaveworks/weave/common" "net/http" - "strings" ) -func errorMessages(errors []error) string { - var result []string - for _, err := range errors { - result = append(result, err.Error()) - } - return strings.Join(result, "\n") -} - func (router *Router) HandleHTTP(muxRouter *mux.Router) { muxRouter.Methods("POST").Path("/connect").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -22,7 +14,7 @@ func (router *Router) HandleHTTP(muxRouter *mux.Router) { http.Error(w, fmt.Sprint("unable to parse form: ", err), http.StatusBadRequest) } if errors := router.ConnectionMaker.InitiateConnections(r.Form["peer"], r.FormValue("replace") == "true"); len(errors) > 0 { - http.Error(w, errorMessages(errors), http.StatusBadRequest) + http.Error(w, common.ErrorMessages(errors), http.StatusBadRequest) } }) diff --git a/router/json.go b/router/json.go deleted file mode 100644 index fd988067..00000000 --- a/router/json.go +++ /dev/null @@ -1,116 +0,0 @@ -package router - -import ( - "encoding/json" - "fmt" - "time" -) - -func (router *Router) StatusJSON(version string) ([]byte, error) { - return json.Marshal(struct { - Version string - Encryption bool - PeerDiscovery bool - Name string - NickName string - Interface string - Macs *MacCache - Peers *Peers - Routes *Routes - ConnectionMakerStatus - }{version, router.UsingPassword(), router.PeerDiscovery, router.Ourself.Name.String(), router.Ourself.NickName, fmt.Sprintf("%v", router.Iface), router.Macs, router.Peers, router.Routes, router.ConnectionMaker.Status()}) -} - -func (cache *MacCache) MarshalJSON() ([]byte, error) { - type cacheEntry struct { - Mac string - Name string - NickName string - LastSeen time.Time - } - var entries []*cacheEntry - for key, entry := range cache.table { - entries = append(entries, &cacheEntry{intmac(key).String(), entry.peer.Name.String(), entry.peer.NickName, entry.lastSeen}) - } - return json.Marshal(entries) -} - -func (peers *Peers) MarshalJSON() ([]byte, error) { - type p struct { - Name string - NickName string - UID PeerUID - Version uint64 - Connections []Connection - } - var ps []*p - peers.ForEach(func(peer *Peer) { - var connections []Connection - if peer == peers.ourself.Peer { - for conn := range peers.ourself.Connections() { - connections = append(connections, conn) - } - } else { - // Modifying peer.connections requires a write lock on - // Peers, and since we are holding a read lock (due to the - // ForEach), access without locking the peer is safe. - for _, conn := range peer.connections { - connections = append(connections, conn) - } - } - ps = append(ps, &p{peer.Name.String(), peer.NickName, peer.UID, peer.version, connections}) - }) - return json.Marshal(ps) -} - -func (routes *Routes) MarshalJSON() ([]byte, error) { - routes.RLock() - defer routes.RUnlock() - type uni struct { - Dest, Via PeerName - } - type broad struct { - Source PeerName - Via []PeerName - } - var r struct { - Unicast []*uni - Broadcast []*broad - } - for name, hop := range routes.unicast { - r.Unicast = append(r.Unicast, &uni{name, hop}) - } - for name, hops := range routes.broadcast { - r.Broadcast = append(r.Broadcast, &broad{name, hops}) - } - return json.Marshal(r) -} - -func (conn *RemoteConnection) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Name string - NickName string - TCPAddr string - }{conn.Remote().Name.String(), conn.Remote().NickName, conn.RemoteTCPAddr()}) -} - -func (name PeerName) MarshalJSON() ([]byte, error) { - return json.Marshal(name.String()) -} - -func (target Target) MarshalJSON() ([]byte, error) { - t := struct { - Attempting bool `json:"Attempting,omitempty"` - TryAfter time.Time `json:"TryAfter,omitempty"` - TryInterval string `json:"TryInterval,omitempty"` - LastError string `json:"LastError,omitempty"` - }{ - Attempting: target.attempting, - TryAfter: target.tryAfter, - TryInterval: target.tryInterval.String(), - } - if target.lastError != nil { - t.LastError = target.lastError.Error() - } - return json.Marshal(t) -} diff --git a/router/mac_cache.go b/router/mac_cache.go index 5eb03f41..021030c9 100644 --- a/router/mac_cache.go +++ b/router/mac_cache.go @@ -1,8 +1,6 @@ package router import ( - "bytes" - "fmt" "net" "sync" "time" @@ -82,16 +80,6 @@ func (cache *MacCache) Delete(peer *Peer) bool { return found } -func (cache *MacCache) String() string { - var buf bytes.Buffer - cache.RLock() - defer cache.RUnlock() - for key, entry := range cache.table { - fmt.Fprintf(&buf, "%v -> %s (%v)\n", intmac(key), entry.peer, entry.lastSeen) - } - return buf.String() -} - func (cache *MacCache) setExpiryTimer() { cache.expiryTimer = time.AfterFunc(cache.maxAge/10, func() { cache.expire() }) } diff --git a/router/peers.go b/router/peers.go index 82b7d3d8..0622eef5 100644 --- a/router/peers.go +++ b/router/peers.go @@ -3,7 +3,6 @@ package router import ( "bytes" "encoding/gob" - "fmt" "io" "sync" ) @@ -170,34 +169,6 @@ func (peers *Peers) GarbageCollect() []*Peer { return removed } -func (peers *Peers) String() string { - var buf bytes.Buffer - printConnection := func(conn Connection) { - established := "" - if !conn.Established() { - established = " (unestablished)" - } - fmt.Fprintf(&buf, " -> %s [%v%s]\n", conn.Remote(), conn.RemoteTCPAddr(), established) - } - peers.ForEach(func(peer *Peer) { - if peer == peers.ourself.Peer { - fmt.Fprintln(&buf, peers.ourself.Info()) - for conn := range peers.ourself.Connections() { - printConnection(conn) - } - } else { - fmt.Fprintln(&buf, peer.Info()) - // Modifying peer.connections requires a write lock on - // Peers, and since we are holding a read lock (due to the - // ForEach), access without locking the peer is safe. - for _, conn := range peer.connections { - printConnection(conn) - } - } - }) - return buf.String() -} - func (peers *Peers) garbageCollect() []*Peer { removed := []*Peer{} peers.ourself.RLock() diff --git a/router/router.go b/router/router.go index 24109a99..c0aff8a3 100644 --- a/router/router.go +++ b/router/router.go @@ -115,26 +115,6 @@ func (router *Router) UsingPassword() bool { return router.Password != nil } -func (router *Router) Status() string { - var buf bytes.Buffer - fmt.Fprintln(&buf, "Our name is", router.Ourself) - fmt.Fprintln(&buf, "Encryption", onOff(router.UsingPassword())) - fmt.Fprintln(&buf, "Peer discovery", onOff(router.PeerDiscovery)) - fmt.Fprintln(&buf, "Sniffing traffic on", router.Iface) - fmt.Fprintf(&buf, "MACs:\n%s", router.Macs) - fmt.Fprintf(&buf, "Peers:\n%s", router.Peers) - fmt.Fprintf(&buf, "Routes:\n%s", router.Routes) - fmt.Fprint(&buf, router.ConnectionMaker.Status()) - return buf.String() -} - -func onOff(b bool) string { - if b { - return "on" - } - return "off" -} - func (router *Router) sniff(pio PacketSourceSink) { log.Println("Sniffing traffic on", router.Iface) diff --git a/router/routes.go b/router/routes.go index 1f73833d..bd8c3531 100644 --- a/router/routes.go +++ b/router/routes.go @@ -1,8 +1,6 @@ package router import ( - "bytes" - "fmt" "math" "sync" ) @@ -109,23 +107,6 @@ func (routes *Routes) RandomNeighbours(except PeerName) PeerNameSet { return res } -func (routes *Routes) String() string { - var buf bytes.Buffer - routes.RLock() - defer routes.RUnlock() - fmt.Fprintln(&buf, "unicast:") - for name, hop := range routes.unicast { - fmt.Fprintf(&buf, "%s -> %s\n", name, hop) - } - fmt.Fprintln(&buf, "broadcast:") - for name, hops := range routes.broadcast { - fmt.Fprintf(&buf, "%s -> %v\n", name, hops) - } - // We don't include the 'all' routes here since they are of - // limited utility in troubleshooting - return buf.String() -} - // Request recalculation of the routing table. This is async but can // effectively be made synchronous with a subsequent call to // EnsureRecalculated. diff --git a/router/status.go b/router/status.go new file mode 100644 index 00000000..65896235 --- /dev/null +++ b/router/status.go @@ -0,0 +1,215 @@ +package router + +import ( + "time" +) + +type Status struct { + Protocol string + ProtocolMinVersion int + ProtocolMaxVersion int + Encryption bool + PeerDiscovery bool + Name string + NickName string + Port int + Interface string + MACs []MACStatus + Peers []PeerStatus + UnicastRoutes []UnicastRouteStatus + BroadcastRoutes []BroadcastRouteStatus + Connections []LocalConnectionStatus + Targets []string +} + +type MACStatus struct { + Mac string + Name string + NickName string + LastSeen time.Time +} + +type PeerStatus struct { + Name string + NickName string + UID PeerUID + Version uint64 + Connections []ConnectionStatus +} + +type ConnectionStatus struct { + Name string + NickName string + Address string + Outbound bool + Established bool +} + +type UnicastRouteStatus struct { + Dest, Via string +} + +type BroadcastRouteStatus struct { + Source string + Via []string +} + +type LocalConnectionStatus struct { + Address string + Outbound bool + State string + Info string +} + +func NewStatus(router *Router) *Status { + var ifaceName string + if router.Iface != nil { + ifaceName = router.Iface.Name + } + return &Status{ + Protocol, + ProtocolMinVersion, + ProtocolMaxVersion, + router.UsingPassword(), + router.PeerDiscovery, + router.Ourself.Name.String(), + router.Ourself.NickName, + router.Port, + ifaceName, + NewMACStatusSlice(router.Macs), + NewPeerStatusSlice(router.Peers), + NewUnicastRouteStatusSlice(router.Routes), + NewBroadcastRouteStatusSlice(router.Routes), + NewLocalConnectionStatusSlice(router.ConnectionMaker), + NewTargetSlice(router.ConnectionMaker)} +} + +func NewMACStatusSlice(cache *MacCache) []MACStatus { + cache.RLock() + defer cache.RUnlock() + + var slice []MACStatus + for key, entry := range cache.table { + slice = append(slice, MACStatus{ + intmac(key).String(), + entry.peer.Name.String(), + entry.peer.NickName, + entry.lastSeen}) + } + + return slice +} + +func NewPeerStatusSlice(peers *Peers) []PeerStatus { + var slice []PeerStatus + + peers.ForEach(func(peer *Peer) { + var connections []ConnectionStatus + if peer == peers.ourself.Peer { + for conn := range peers.ourself.Connections() { + connections = append(connections, newConnectionStatus(conn)) + } + } else { + // Modifying peer.connections requires a write lock on + // Peers, and since we are holding a read lock (due to the + // ForEach), access without locking the peer is safe. + for _, conn := range peer.connections { + connections = append(connections, newConnectionStatus(conn)) + } + } + slice = append(slice, PeerStatus{ + peer.Name.String(), + peer.NickName, + peer.UID, + peer.version, + connections}) + }) + + return slice +} + +func newConnectionStatus(c Connection) ConnectionStatus { + return ConnectionStatus{ + c.Remote().Name.String(), + c.Remote().NickName, + c.RemoteTCPAddr(), + c.Outbound(), + c.Established()} +} + +func NewUnicastRouteStatusSlice(routes *Routes) []UnicastRouteStatus { + routes.RLock() + defer routes.RUnlock() + + var slice []UnicastRouteStatus + for dest, via := range routes.unicast { + slice = append(slice, UnicastRouteStatus{dest.String(), via.String()}) + } + return slice +} + +func NewBroadcastRouteStatusSlice(routes *Routes) []BroadcastRouteStatus { + routes.RLock() + defer routes.RUnlock() + + var slice []BroadcastRouteStatus + for source, via := range routes.broadcast { + var hops []string + for _, hop := range via { + hops = append(hops, hop.String()) + } + slice = append(slice, BroadcastRouteStatus{source.String(), hops}) + } + return slice +} + +func NewLocalConnectionStatusSlice(cm *ConnectionMaker) []LocalConnectionStatus { + // We need to Refresh first in order to clear out any 'attempting' + // connections from cm.targets that have been established since + // the last run of cm.checkStateAndAttemptConnections. These + // entries are harmless but do represent stale state that we do + // not want to report. + cm.Refresh() + resultChan := make(chan []LocalConnectionStatus, 0) + cm.actionChan <- func() bool { + var slice []LocalConnectionStatus + for conn := range cm.ourself.Connections() { + state := "pending" + if conn.Established() { + state = "established" + } + slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, conn.Remote().String()}) + } + for address, target := range cm.targets { + var state, info string + switch { + case target.lastError == nil: + state = "connecting" + info = "" + case target.attempting: + state = "retrying" + info = target.lastError.Error() + default: + state = "failed" + info = target.lastError.Error() + ", retry: " + target.tryAfter.String() + } + slice = append(slice, LocalConnectionStatus{address, true, state, info}) + } + resultChan <- slice + return false + } + return <-resultChan +} + +func NewTargetSlice(cm *ConnectionMaker) []string { + resultChan := make(chan []string, 0) + cm.actionChan <- func() bool { + var slice []string + for peer := range cm.directPeers { + slice = append(slice, peer) + } + resultChan <- slice + return false + } + return <-resultChan +} diff --git a/site/features.md b/site/features.md index cc9d6126..ad66426d 100644 --- a/site/features.md +++ b/site/features.md @@ -498,6 +498,11 @@ be disabled with the `--no-discovery` option to `weave launch`. In this mode, weave will only connect to the addresses specified at launch time and with `weave connect`. +The list of all hosts that a peer has been asked to connect to with +`weave launch` and `weave connect` can be obtained with + + host# weave status targets + ### Container mobility Containers can be moved between hosts without requiring any diff --git a/site/ipam.md b/site/ipam.md index d2f21f73..9d4a8b83 100644 --- a/site/ipam.md +++ b/site/ipam.md @@ -48,6 +48,17 @@ time, you can give the number of peers like this: host3$ weave launch --init-peer-count 3 $HOST2 +The consensus mechanism used to determine a majority transitions +through three states: 'deferred', 'waiting' and 'achieved': + +* 'deferred' - no allocation requests or claims have been made yet; + consensus is deferred until then +* 'waiting' - an attempt to achieve consensus is ongoing, triggered by + an allocation or claim request; allocations will block. This state + persists until a quorum of peers are able to communicate amongst + themselves successfully +* 'achieved' - consensus achieved; allocations proceed normally + ### More on `--init-peer-count` TL;DR: it isn't a problem to over-estimate by a bit, but if you supply @@ -199,21 +210,16 @@ reports on the current status of the weave router and IP allocator: ```` ... -Allocator universe 10.2.0.0/16 -Owned Ranges: - 10.2.1.1 -> 96:e9:e2:2e:2d:bc (host1) (v3) - 10.2.1.128 -> ea:84:25:9b:31:2e (host2) (v3) - 10.2.1.192 -> ea:6c:21:09:cf:f0 (host3) (v9) -Allocator default subnet: 10.2.1.0/24 + Service: ipam + Consensus: waiting(quorum: 2, known: 0) + Range: [10.32.0.0-10.48.0.0) + DefaultSubnet: 10.32.0.0/12 ... ```` The first section covers the router; see the [troubleshooting -guide](troubleshooting.html#status-report) for full details. +guide](troubleshooting.html#weave-status) for full details. -The 'Allocator' section summarises the overall position and lists -which address ranges have been assigned to which peer. Each range -begins at the address shown and ends just before the next address, or -wraps around at the end of the subnet. The 'v' number denotes how many -times that entry has been updated. +The 'Service: ipam' section displays the consensus state as well as +the total allocation range and default subnet. diff --git a/site/troubleshooting.md b/site/troubleshooting.md index 30da1e07..b977895e 100644 --- a/site/troubleshooting.md +++ b/site/troubleshooting.md @@ -5,6 +5,14 @@ layout: default # Troubleshooting Weave + * [Overall status](#weave-status) + * [List connections](#weave-status-connections) + * [List peers](#weave-status-peers) + * [List DNS entries](#weave-status-dns) + * [JSON report](#weave-report) + * [List attached containers](#list-attached-containers) + * [Snapshot releases](#snapshot-releases) + Check what version of weave you are running with weave version @@ -29,9 +37,6 @@ Another useful debugging technique is to attach standard packet capture and analysis tools, such as tcpdump and wireshark, to the `weave` network bridge on the host. -One can ask a weave router for a [status report](#status-report) or -for a [list of attached containers](#list-attached-containers). - To stop weave, run weave stop @@ -50,100 +55,169 @@ Any running application containers will permanently lose connectivity with the weave network and have to be restarted in order to re-connect. -### Status report +### Overall status -The command - - weave status - -reports on the current status of the weave router and DNS. - -This produces output like: +A status summary can be obtained with `weave status`: ```` -weave router 0.7.0 -Our name is 7a:f4:56:87:76:3b(weave01) -Encryption off -Peer discovery on -Sniffing traffic on &{39 65535 ethwe ae:e3:07:9c:8c:d4 up|broadcast|multicast} -MACs: -ba:8c:b9:dc:e1:c9 -> 7a:f4:56:87:76:3b(weave01) (2014-10-23 16:39:19.482338935 +0000 UTC) -ce:15:34:a9:b5:6d -> 7a:f4:56:87:76:3b(weave01) (2014-10-23 16:39:28.257103595 +0000 UTC) -7a:61:a2:49:4b:91 -> 7a:f4:56:87:76:3b(weave01) (2014-10-23 16:39:27.482970752 +0000 UTC) -9e:95:0c:54:8e:39 -> 7a:16:dd:5b:83:de(weave02) (2014-10-23 16:39:28.795601325 +0000 UTC) -72:5f:a4:60:e5:ce -> 7a:16:dd:5b:83:de(weave02) (2014-10-23 16:39:29.575995255 +0000 UTC) -Peers: -7a:16:dd:5b:83:de(weave02) (v31) (UID 13151318985609435078) - -> 7a:f4:56:87:76:3b(weave01) [37.157.33.76:7195] -7a:f4:56:87:76:3b(weave01) (v1) (UID 6913268221365110570) - -> 7a:16:dd:5b:83:de(weave02) [191.235.147.190:6783] -Routes: -unicast: -7a:f4:56:87:76:3b -> 00:00:00:00:00:00 -7a:16:dd:5b:83:de -> 7a:16:dd:5b:83:de -broadcast: -7a:f4:56:87:76:3b -> [7a:16:dd:5b:83:de] -7a:16:dd:5b:83:de -> [] -Direct Peers: 191.235.147.190 192.168.32.1 -Reconnects: -->[192.168.32.1:6783] (dial tcp4 192.168.32.1:6783: connection timed out) next try at 2014-10-23 16:39:50.585932102 +0000 UTC +$ weave status + Version: 1.1.0 + + Service: router + Protocol: weave 1..2 + Name: 4a:0f:f6:ec:1c:93(host1) + Encryption: disabled + PeerDiscovery: enabled + Targets: [192.168.48.14 192.168.48.15] + Connections: 5 (1 established, 1 pending, 1 retrying, 1 failed, 1 connecting) + Peers: 3 (with 5 established, 1 pending connections betweeen them) + + Service: ipam + Consensus: achieved + Range: [10.32.0.0-10.48.0.0) + DefaultSubnet: 10.32.0.0/12 + + Service: dns + Domain: weave.local. + TTL: 1 + Entries: 9 + + Service: proxy + Address: tcp://127.0.0.1:12375 ```` The terms used here are explained further at [how it works](how-it-works.html). -The 'Our name' line identifies the local weave router as a peer in the -weave network, displaying the peer name followed by the peer's nickname -in parenthesis. The nickname defaults to the name of the host on which -the weave container was launched; if desired it can be overriden by -supplying the `--nickname` argument to `weave launch`. +The 'Version' line shows the weave version. -The 'Sniffing traffic' line shows details of the virtual ethernet -interface that weave is using to receive packets on the local -machine. +The 'Protocol' line indicates the weave router's inter-peer +communication protocol name and supported versions (min..max). + +The 'Name' line identifies the local weave router as a peer in the +weave network. The nickname shown in parentheses defaults to the name +of the host on which the weave container was launched; if desired it +can be overriden by supplying the `--nickname` argument to `weave +launch`. The 'Encryption' line indicates whether [encryption](features.html#security) is in use for communication between peers. -The 'Peer discovery' line indicates whether +The 'PeerDiscovery' line indicates whether [automatic peer discovery](features.html#dynamic-topologies) is enabled (which is the default). -The 'MACs' section lists all MAC addresses known to this router. These -identify containers in the weave network, as well as points for -[host network integration](features.html#host-network-integration). For -each MAC the list shows the peer they reside on, and the time when the -router last saw some traffic from that MAC. The router forgets -addresses which are inactive for longer than 10 minutes. +'Targets' is the number of hosts that the local weave router has been +asked to connect to in `weave launch` and `weave connect`. The +complete list can be obtained with `weave status targets`. -The 'Peers' section lists all peers known to this router, including -itself. Each peer is shown with its name, nickname, version number -(incremented on each reconnect) and the UID. Then each line -beginning `->` shows another peer that it is connected to, with the -IP address and port number of the connection. In the above example, -the local router has connected to its peer using address -191.235.147.190:6783, and its peer sees the same connection as coming -from 37.157.33.76:7195. +'Connections' shows the total number connections between the local weave +router and other peers, and a break down of that figure by connection +state. Further details are available with +[`weave status connections`](#weave-status-connections). -The 'Routes' section summarised the information for deciding how to -route packets between peers, which is mostly of interest when the -weave network is not fully connected. See the -[architecture documentation](https://raw.githubusercontent.com/weaveworks/weave/master/docs/architecture.txt) -for a full explanation. +'Peers' shows the total number of peers in the network, and the number of +connections between them. Further details are available with +[`weave status peers`](#weave-status-peers). -'Direct Peers' lists the hosts which weave was told to connect to in -`weave launch` and `weave connect`. +There are further sections for the [IP address +allocator](ipam.html#troubleshooting), +[weaveDNS](weavedns.html#troubleshooting), and [Weave Docker API +Proxy](proxy.html#troubleshooting). -The 'Reconnects' section lists peers that this router is aware of, but is -not currently connected to. Each line contains some information about -what went wrong the last time; whether it is attempting to connect or -is waiting for a while before connecting again. +### List connections -There may also be further sections for the -[IP address allocator](ipam.html#troubleshooting), -[weaveDNS](weavedns.html#troubleshooting), and -[Weave Docker API Proxy](proxy.html#troubleshooting). +Detailed information on the local weave router's connections can be +obtained with `weave status connections`: + +```` +$ weave status connections +<- 192.168.48.12:33866 established 7e:21:4a:70:2f:45(host2) +<- 192.168.48.13:60773 pending 7e:ae:cd:d5:23:8d(host3) +-> 192.168.48.14:6783 retrying dial tcp4 192.168.48.14:6783: no route to host +-> 192.168.48.15:6783 failed dial tcp4 192.168.48.15:6783: no route to host, retry: 2015-08-06 18:55:38.246910357 +0000 UTC +-> 192.168.48.16:6783 connecting +```` + +The columns are as follows: + + * Connection origination direction (`->` for outbound, `<-` for + inbound) + * Remote TCP address + * Status + * `connecting` - first connection attempt in progress + * `failed` - TCP connection or UDP heartbeat failed + * `retrying` - retry of a previously failed connection attempt in + progress; reason for previous failure follows + * `pending` - TCP connection up, waiting for confirmation of + UDP heartbeat + * `established` - TCP connection and corresponding UDP path are up + * Info - the remote peer name and nickname for (un)established + connections, the failure reason for failed and retrying connection + +### List peers + +Detailed information on peers can be obtained with `weave status +peers`: + +```` +$ weave status peers +ce:31:e0:06:45:1a(host1) + <- 192.168.48.12:39634 ea:2d:b2:e6:e4:f5(host2) established + <- 192.168.48.13:49619 ee:38:33:a7:d9:71(host3) established +ea:2d:b2:e6:e4:f5(host2) + -> 192.168.48.11:6783 ce:31:e0:06:45:1a(host1) established + <- 192.168.48.13:58181 ee:38:33:a7:d9:71(host3) established +ee:38:33:a7:d9:71(host3) + -> 192.168.48.12:6783 ea:2d:b2:e6:e4:f5(host2) established + -> 192.168.48.11:6783 ce:31:e0:06:45:1a(host1) established +```` + +This lists all peers known to this router, including itself. Each +peer is shown with its name and nickname, then each line thereafter +shows another peer that it is connected to, with the direction, IP +address and port number of the connection. In the above example, +`host3` has connected to `host1` at `192.168.48.11:6783`; `host1` sees +the `host3` end of the same connection as `192.168.48.13:49619`. + +### List DNS entries + +Detailed information on DNS registrations can be obtained with `weave +status dns`: + +```` +$ weave status dns +one 10.32.0.1 eebd81120ee4 4a:0f:f6:ec:1c:93 +one 10.43.255.255 4fcec78d2a9b 66:c4:47:c6:65:bf +one 10.40.0.0 bab69d305cba ba:98:d0:37:4f:1c +three 10.32.0.3 7615b6537f74 4a:0f:f6:ec:1c:93 +three 10.44.0.1 c0b39dc52f8d 66:c4:47:c6:65:bf +three 10.40.0.2 8a9c2e2ef00f ba:98:d0:37:4f:1c +two 10.32.0.2 83689b8f34e0 4a:0f:f6:ec:1c:93 +two 10.44.0.0 7edc306cb668 66:c4:47:c6:65:bf +two 10.40.0.1 68a5e9c2641b ba:98:d0:37:4f:1c +```` + +The columns are as follows: + + * Unqualified hostname + * IPv4 address + * Registering entity identifier (typically a container ID) + * Name of peer from which the registration originates + +### JSON report + + $ weave report + +Produces a comprehensive dump of the internal state of the router, +IPAM and DNS services in JSON format, including all the information +available from the `weave status` commands. You can also supply a +Golang text template to `weave report` in a similar fashion to `docker +inspect`: + + $ weave report -f '{{{.DNS.Domain}}' + weave.local. ### List attached containers diff --git a/site/weavedns.md b/site/weavedns.md index 29a66fbf..ddb44e13 100644 --- a/site/weavedns.md +++ b/site/weavedns.md @@ -247,25 +247,25 @@ DNS: ```` ... -WeaveDNS (d6:1b:df:42:af:7d) - listening on port 53, for domain weave.local. - response ttl 30 - -1d836dea2c56: foo.weave.local. [10.2.2.1] + Service: dns + Domain: weave.local. + TTL: 1 + Entries: 9 ... ```` The first section covers the router; see the [troubleshooting -guide](troubleshooting.html#status-report) for more detail. +guide](troubleshooting.html#weave-status) for more detail. -The second section is pertinent to weaveDNS, and includes: +The 'Service: dns' section is pertinent to weaveDNS, and includes: -* Port on which the DNS server is listening * The local domain suffix which is being served * The response ttl -* The names known to the weaveDNS server. Each entry comprises - the container ID, IP address and its fully qualified domain name +* The total number of entries + +You may also use `weave status dns` to obtain a [complete +dump](troubleshooting.html#weave-status-dns) of all DNS registrations. Information on the processing of queries, and the general operation of weaveDNS, can be obtained from the container logs with diff --git a/test/150_connect_forget_2_test.sh b/test/150_connect_forget_2_test.sh index 194d176b..0ba45f42 100755 --- a/test/150_connect_forget_2_test.sh +++ b/test/150_connect_forget_2_test.sh @@ -6,7 +6,8 @@ C1=10.2.1.4 C2=10.2.1.7 direct_peers() { - weave_on $1 status | sed -n -e 's|^Direct Peers: \(.*\)|\1|p' | tr -s ' ' '\n' + weave_on $1 report -f "{{range .Router.Targets}}{{.}} +{{end}}" } assert_peers() { diff --git a/test/700_status_and_report_test.sh b/test/700_status_and_report_test.sh new file mode 100755 index 00000000..f8d4d4c1 --- /dev/null +++ b/test/700_status_and_report_test.sh @@ -0,0 +1,29 @@ +#! /bin/bash + +. ./config.sh + +UNIVERSE=10.2.0.0/16 + +start_suite "weave status/report" + +weave_on $HOST1 launch --ipalloc-range $UNIVERSE --name 8a:3e:3e:3e:3e:3e --nickname nicknamington + +check() { + assert "weave_on $HOST1 status | grep -oP '(?<= $1: ).*'" "$3" + assert "weave_on $HOST1 report -f '$2'" "$3" +} + +check "Name" "{{.Router.Name}}({{.Router.NickName}})" "8a:3e:3e:3e:3e:3e(nicknamington)" +check "Peers" "{{len .Router.Peers}}" "1" +check "DefaultSubnet" "{{.IPAM.DefaultSubnet}}" $UNIVERSE +check "Domain" "{{.DNS.Domain}}" "weave.local." + +assert_raises "weave_on $HOST1 status peers | grep nicknamington" +weave_on $HOST1 connect 10.2.2.1 +assert "weave_on $HOST1 status targets" "10.2.2.1" +assert "weave_on $HOST1 status connections | tr -s ' ' | cut -d ' ' -f 2" "10.2.2.1:6783" +start_container $HOST1 --name test +assert "weave_on $HOST1 status dns | tr -s ' ' | cut -d ' ' -f 1" "test" +assert_raises "weave_on $HOST1 report | grep nicknamington" + +end_suite diff --git a/weave b/weave index 57bc1aab..61d809dd 100755 --- a/weave +++ b/weave @@ -45,7 +45,8 @@ weave env weave config weave connect [--replace] [ ...] weave forget ... -weave status +weave status [targets | connections | peers | dns] +weave report [-f ] weave run [--with-dns | --without-dns] [ ...] ... weave start [ ...] @@ -1323,14 +1324,29 @@ case "$COMMAND" in ;; status) res=0 - call_weave GET /status || res=1 - echo - if check_running $PROXY_CONTAINER_NAME 2>/dev/null ; then + OMIT_PROXY= + STATUS_URL="/status" + while [ $# -gt 0 ] ; do + OMIT_PROXY=1 + STATUS_URL="$STATUS_URL/$1" + shift + done + call_weave GET $STATUS_URL || res=1 + if [ -z "$OMIT_PROXY" ] && check_running $PROXY_CONTAINER_NAME 2>/dev/null ; then echo - echo "weave proxy is running" + echo " Service: proxy" + echo " Address: $(proxy_addr)" fi [ $res -eq 0 ] ;; + report) + if [ $# -gt 0 ] ; then + [ $# -eq 2 -a "$1" = "-f" ] || usage + call_weave GET /report --get --data-urlencode "format=$2" + else + call_weave GET /report -H 'Accept: application/json' + fi + ;; run) dns_args "$@" shift $(dns_arg_count "$@")