diff --git a/ipam/ring/ring.go b/ipam/ring/ring.go index 30f32b8d..e992dd43 100644 --- a/ipam/ring/ring.go +++ b/ipam/ring/ring.go @@ -316,6 +316,26 @@ func (r *Ring) OwnedRanges() (result []address.Range) { return r.splitRangesOverZero(result) } +func (r *Ring) OwnedRangesByPeer() map[mesh.PeerName][]address.Range { + r.assertInvariants() + + result := make(map[mesh.PeerName][]address.Range) + for i, entry := range r.Entries { + nextEntry := r.Entries.entry(i + 1) + resultPeer, found := result[entry.Peer] + r := address.Range{Start: entry.Token, End: nextEntry.Token} + if !found { + result[entry.Peer] = []address.Range{r} + } else { + result[entry.Peer] = append(resultPeer, r) + } + } + for peer, resultPeer := range result { + result[peer] = r.splitRangesOverZero(resultPeer) + } + return result +} + // ClaimForPeers claims the entire ring for the array of peers passed // in. Only works for empty rings. func (r *Ring) ClaimForPeers(peers []mesh.PeerName) { diff --git a/ipam/status.go b/ipam/status.go index 72df8d3e..39bc4588 100644 --- a/ipam/status.go +++ b/ipam/status.go @@ -2,13 +2,23 @@ package ipam import ( "github.com/weaveworks/weave/ipam/paxos" + "github.com/weaveworks/weave/ipam/ring" + "github.com/weaveworks/weave/ipam/space" + "github.com/weaveworks/weave/mesh" "github.com/weaveworks/weave/net/address" ) type Status struct { Paxos *paxos.Status Range string + RangeNumIPs int DefaultSubnet string + Ring *ring.Ring // information on ranges owned by all peers + Space space.Space // more detail on ranges owned by us + Owned map[string][]address.Address // addresses by container-ID + OurName mesh.PeerName + Nicknames map[mesh.PeerName]string `json:"-"` + IsKnownPeer func(mesh.PeerName) bool `json:"-"` Entries []EntryStatus PendingClaims []ClaimStatus PendingAllocates []string @@ -40,7 +50,14 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status { resultChan <- &Status{ paxosStatus, allocator.universe.String(), + int(allocator.universe.Size()), defaultSubnet.String(), + allocator.ring, + allocator.space, + allocator.owned, + allocator.ourName, + allocator.nicknames, + allocator.isKnownPeer, newEntryStatusSlice(allocator), newClaimStatusSlice(allocator), newAllocateIdentSlice(allocator)} diff --git a/prog/weaver/http.go b/prog/weaver/http.go index 6794967e..848afbc5 100644 --- a/prog/weaver/http.go +++ b/prog/weaver/http.go @@ -1,8 +1,13 @@ package main import ( + "bytes" "encoding/json" "fmt" + "net/http" + "strings" + "text/template" + "github.com/gorilla/mux" . "github.com/weaveworks/weave/common" "github.com/weaveworks/weave/ipam" @@ -10,9 +15,6 @@ import ( "github.com/weaveworks/weave/nameserver" "github.com/weaveworks/weave/net/address" weave "github.com/weaveworks/weave/router" - "net/http" - "strings" - "text/template" ) var rootTemplate = template.New("root").Funcs(map[string]interface{}{ @@ -31,6 +33,76 @@ var rootTemplate = template.New("root").Funcs(map[string]interface{}{ } return strings.Join(servers, ", ") }, + "printIPAMRanges": func(status ipam.Status) string { + var buffer bytes.Buffer + + type NicknameStats struct { + Ranges []address.Range + Addresses []address.Address + Reachable bool + } + + nicknameStats := make(map[string]*NicknameStats) + getNickName := func(peer mesh.PeerName) string { + name, found := status.Nicknames[peer] + if !found { + return fmt.Sprintf("%s", peer) + } + return name + } + getOrCreateNicknameStats := func(peer mesh.PeerName) *NicknameStats { + peerName := getNickName(peer) + stats, found := nicknameStats[peerName] + if !found { + stats = &NicknameStats{ + Ranges: []address.Range{}, + Addresses: []address.Address{}, + Reachable: status.IsKnownPeer(peer), + } + nicknameStats[peerName] = stats + } + return stats + } + + for _, entry := range status.Ring.Entries { + stats := getOrCreateNicknameStats(entry.Peer) + stats.Addresses = append(stats.Addresses, entry.Token) + } + + for peer, ranges := range status.Ring.OwnedRangesByPeer() { + stats := getOrCreateNicknameStats(peer) + stats.Ranges = append(stats.Ranges, ranges...) + } + + appendAddresses := func(displayName string, reachable bool, addresses []address.Address, ranges []address.Range) { + reachableStr := "" + if !reachable { + reachableStr = "- unreachable!" + } + ipsInRange := 0 + for _, chunk := range ranges { + ipsInRange += int(chunk.Size()) + } + percentageRanges := float32(ipsInRange) * 100.0 / float32(status.RangeNumIPs) + + fmt.Fprintf(&buffer, "%20s: %8d IPs (%04.1f%% of universe, in %3d ranges) - used: %8d IPs %s\n", + displayName, ipsInRange, percentageRanges, len(ranges), len(addresses), reachableStr) + } + + // print the local addresses + ourNickname := getNickName(status.OurName) + ourStats := nicknameStats[ourNickname] + appendAddresses("(local)", true, ourStats.Addresses, ourStats.Ranges) + + // and then the rest + for nickname, stats := range nicknameStats { + if nickname != ourNickname { + appendAddresses(nickname, stats.Reachable, stats.Addresses, stats.Ranges) + } + } + + return buffer.String() + }, "printConnectionCounts": func(conns []mesh.LocalConnectionStatus) string { counts := make(map[string]int) for _, conn := range conns { @@ -152,6 +224,25 @@ var dnsEntriesTemplate = defTemplate("dnsEntries", `\ {{end}}\ `) +var ipamTemplate = defTemplate("ipamTemplate", `\ + Universe: {{.IPAM.Range}} ({{.IPAM.RangeNumIPs}} IPs) + DefaultSubnet: {{.IPAM.DefaultSubnet}} +{{if .IPAM.Owned}}\ + Containers: +{{range $id, $addresses := .IPAM.Owned}}\ + {{printf "%-12s" $id}}: {{$addresses}} +{{end}}\ +{{end}}\ + Ownerships: +{{printIPAMRanges .IPAM}} +{{if .IPAM.PendingClaims}}\ + Claims: +{{range .IPAM.PendingClaims}} + {{printf "%-15v" .Ident}} {{.Address}} +{{end}}\ +{{end}}\ +`) + type WeaveStatus struct { Version string Router *weave.NetworkRouterStatus `json:"Router,omitempty"` @@ -207,5 +298,6 @@ func HandleHTTP(muxRouter *mux.Router, version string, router *weave.NetworkRout defHandler("/status/connections", connectionsTemplate) defHandler("/status/peers", peersTemplate) defHandler("/status/dns", dnsEntriesTemplate) + defHandler("/status/ipam", ipamTemplate) }