Propagate network info for containers sharing network namespaces (#1401)

- Add armon/go-radix library, use this to find containers by prefix
- Deal with host net namespace in the same way
This commit is contained in:
Tom Wilkie
2016-04-29 18:13:55 +01:00
parent a81b116672
commit 02554b1dcd
12 changed files with 929 additions and 191 deletions

View File

@@ -100,13 +100,15 @@ type Container interface {
Image() string
PID() int
Hostname() string
GetNode([]net.IP) report.Node
GetNode() report.Node
State() string
StateString() string
HasTTY() bool
Container() *docker.Container
StartGatheringStats() error
StopGatheringStats()
NetworkMode() (string, bool)
NetworkInfo([]net.IP) report.Sets
}
type container struct {
@@ -284,6 +286,39 @@ func (c *container) ports(localAddrs []net.IP) report.StringSet {
return report.MakeStringSet(ports...)
}
func (c *container) NetworkMode() (string, bool) {
c.RLock()
defer c.RUnlock()
if c.container.HostConfig != nil {
return c.container.HostConfig.NetworkMode, true
}
return "", false
}
func addScopeToIPs(hostID string, ips []string) []string {
ipsWithScopes := []string{}
for _, ip := range ips {
ipsWithScopes = append(ipsWithScopes, report.MakeScopedAddressNodeID(hostID, ip))
}
return ipsWithScopes
}
func (c *container) NetworkInfo(localAddrs []net.IP) report.Sets {
c.RLock()
defer c.RUnlock()
ips := c.container.NetworkSettings.SecondaryIPAddresses
if c.container.NetworkSettings.IPAddress != "" {
ips = append(ips, c.container.NetworkSettings.IPAddress)
}
// Treat all Docker IPs as local scoped.
ipsWithScopes := addScopeToIPs(c.hostID, ips)
return report.EmptySets.
Add(ContainerPorts, c.ports(localAddrs)).
Add(ContainerIPs, report.MakeStringSet(ips...)).
Add(ContainerIPsWithScopes, report.MakeStringSet(ipsWithScopes...))
}
func (c *container) memoryUsageMetric(stats []docker.Stats) report.Metric {
result := report.MakeMetric()
for _, s := range stats {
@@ -345,19 +380,9 @@ func (c *container) env() map[string]string {
return result
}
func (c *container) GetNode(localAddrs []net.IP) report.Node {
func (c *container) GetNode() report.Node {
c.RLock()
defer c.RUnlock()
ips := c.container.NetworkSettings.SecondaryIPAddresses
if c.container.NetworkSettings.IPAddress != "" {
ips = append(ips, c.container.NetworkSettings.IPAddress)
}
// Treat all Docker IPs as local scoped.
ipsWithScopes := []string{}
for _, ip := range ips {
ipsWithScopes = append(ipsWithScopes, report.MakeScopedAddressNodeID(c.hostID, ip))
}
result := report.MakeNodeWith(report.MakeContainerNodeID(c.ID()), map[string]string{
ContainerID: c.ID(),
ContainerName: strings.TrimPrefix(c.container.Name, "/"),
@@ -367,11 +392,7 @@ func (c *container) GetNode(localAddrs []net.IP) report.Node {
ContainerHostname: c.Hostname(),
ContainerState: c.StateString(),
ContainerStateHuman: c.State(),
}).WithSets(report.EmptySets.
Add(ContainerPorts, c.ports(localAddrs)).
Add(ContainerIPs, report.MakeStringSet(ips...)).
Add(ContainerIPsWithScopes, report.MakeStringSet(ipsWithScopes...)),
).WithMetrics(
}).WithMetrics(
c.metrics(),
).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID(c.Image()))),

View File

@@ -74,42 +74,51 @@ func TestContainer(t *testing.T) {
}
// Now see if we go them
uptime := (now.Sub(startTime) / time.Second) * time.Second
want := report.MakeNodeWith("ping;<container>", map[string]string{
"docker_container_command": " ",
"docker_container_created": "01 Jan 01 00:00 UTC",
"docker_container_id": "ping",
"docker_container_name": "pong",
"docker_image_id": "baz",
"docker_label_foo1": "bar1",
"docker_label_foo2": "bar2",
"docker_container_state": "running",
"docker_container_state_human": "Up 6 years",
"docker_container_uptime": uptime.String(),
}).
WithSets(report.EmptySets.
{
uptime := (now.Sub(startTime) / time.Second) * time.Second
want := report.MakeNodeWith("ping;<container>", map[string]string{
"docker_container_command": " ",
"docker_container_created": "01 Jan 01 00:00 UTC",
"docker_container_id": "ping",
"docker_container_name": "pong",
"docker_image_id": "baz",
"docker_label_foo1": "bar1",
"docker_label_foo2": "bar2",
"docker_container_state": "running",
"docker_container_state_human": "Up 6 years",
"docker_container_uptime": uptime.String(),
}).
WithControls(
docker.RestartContainer, docker.StopContainer, docker.PauseContainer,
docker.AttachContainer, docker.ExecContainer,
).WithMetrics(report.Metrics{
"docker_cpu_total_usage": report.MakeMetric(),
"docker_memory_usage": report.MakeMetric().Add(now, 12345).WithMax(45678),
}).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID("baz"))),
)
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
node := c.GetNode()
node.Latest.ForEach(func(k, v string) {
if v == "0" || v == "" {
node.Latest = node.Latest.Delete(k)
}
})
return node
})
}
{
want := report.EmptySets.
Add("docker_container_ports", report.MakeStringSet("1.2.3.4:80->80/tcp", "81/tcp")).
Add("docker_container_ips", report.MakeStringSet("1.2.3.4")).
Add("docker_container_ips_with_scopes", report.MakeStringSet("scope;1.2.3.4")),
).WithControls(
docker.RestartContainer, docker.StopContainer, docker.PauseContainer,
docker.AttachContainer, docker.ExecContainer,
).WithMetrics(report.Metrics{
"docker_cpu_total_usage": report.MakeMetric(),
"docker_memory_usage": report.MakeMetric().Add(now, 12345).WithMax(45678),
}).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID("baz"))),
)
Add("docker_container_ips_with_scopes", report.MakeStringSet("scope;1.2.3.4"))
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
node := c.GetNode([]net.IP{})
node.Latest.ForEach(func(k, v string) {
if v == "0" || v == "" {
node.Latest = node.Latest.Delete(k)
}
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
return c.NetworkInfo([]net.IP{})
})
return node
})
}
if c.Image() != "baz" {
t.Errorf("%s != baz", c.Image())
@@ -117,7 +126,8 @@ func TestContainer(t *testing.T) {
if c.PID() != 2 {
t.Errorf("%d != 2", c.PID())
}
if have := docker.ExtractContainerIPs(c.GetNode([]net.IP{})); !reflect.DeepEqual(have, []string{"1.2.3.4"}) {
node := c.GetNode().WithSets(c.NetworkInfo([]net.IP{}))
if have := docker.ExtractContainerIPs(node); !reflect.DeepEqual(have, []string{"1.2.3.4"}) {
t.Errorf("%v != %v", have, []string{"1.2.3.4"})
}
}

View File

@@ -5,6 +5,7 @@ import (
"time"
log "github.com/Sirupsen/logrus"
"github.com/armon/go-radix"
docker_client "github.com/fsouza/go-dockerclient"
"github.com/weaveworks/scope/probe/controls"
@@ -37,6 +38,7 @@ type Registry interface {
WalkImages(f func(*docker_client.APIImages))
WatchContainerUpdates(ContainerUpdateWatcher)
GetContainer(string) (Container, bool)
GetContainerByPrefix(string) (Container, bool)
}
// ContainerUpdateWatcher is the type of functions that get called when containers are updated.
@@ -52,7 +54,7 @@ type registry struct {
hostID string
watchers []ContainerUpdateWatcher
containers map[string]Container
containers *radix.Tree
containersByPID map[int]Container
images map[string]*docker_client.APIImages
}
@@ -88,7 +90,7 @@ func NewRegistry(interval time.Duration, pipes controls.PipeClient, collectStats
}
r := &registry{
containers: map[string]Container{},
containers: radix.New(),
containersByPID: map[int]Container{},
images: map[string]*docker_client.APIImages{},
@@ -186,9 +188,10 @@ func (r *registry) listenForEvents() bool {
defer r.Unlock()
if r.collectStats {
for _, c := range r.containers {
c.StopGatheringStats()
}
r.containers.Walk(func(_ string, c interface{}) bool {
c.(Container).StopGatheringStats()
return false
})
}
close(ch)
return false
@@ -201,12 +204,13 @@ func (r *registry) reset() {
defer r.Unlock()
if r.collectStats {
for _, c := range r.containers {
c.StopGatheringStats()
}
r.containers.Walk(func(_ string, c interface{}) bool {
c.(Container).StopGatheringStats()
return false
})
}
r.containers = map[string]Container{}
r.containers = radix.New()
r.containersByPID = map[int]Container{}
r.images = map[string]*docker_client.APIImages{}
}
@@ -270,12 +274,13 @@ func (r *registry) updateContainerState(containerID string, intendedState *strin
}
// Container doesn't exist anymore, so lets stop and remove it
container, ok := r.containers[containerID]
c, ok := r.containers.Get(containerID)
if !ok {
return
}
container := c.(Container)
delete(r.containers, containerID)
r.containers.Delete(containerID)
delete(r.containersByPID, container.PID())
if r.collectStats {
container.StopGatheringStats()
@@ -295,11 +300,13 @@ func (r *registry) updateContainerState(containerID string, intendedState *strin
}
// Container exists, ensure we have it
c, ok := r.containers[containerID]
o, ok := r.containers.Get(containerID)
var c Container
if !ok {
c = NewContainerStub(dockerContainer, r.hostID)
r.containers[containerID] = c
r.containers.Insert(containerID, c)
} else {
c = o.(Container)
// potentially remove existing pid mapping.
delete(r.containersByPID, c.PID())
c.UpdateState(dockerContainer)
@@ -311,9 +318,8 @@ func (r *registry) updateContainerState(containerID string, intendedState *strin
}
// Trigger anyone watching for updates
localAddrs, err := report.LocalAddresses()
if err != nil {
node := c.GetNode(localAddrs)
node := c.GetNode()
for _, f := range r.watchers {
f(node)
}
@@ -350,16 +356,34 @@ func (r *registry) WalkContainers(f func(Container)) {
r.RLock()
defer r.RUnlock()
for _, container := range r.containers {
f(container)
}
r.containers.Walk(func(_ string, c interface{}) bool {
f(c.(Container))
return false
})
}
func (r *registry) GetContainer(id string) (Container, bool) {
r.RLock()
defer r.RUnlock()
c, ok := r.containers[id]
return c, ok
c, ok := r.containers.Get(id)
if ok {
return c.(Container), true
}
return nil, false
}
func (r *registry) GetContainerByPrefix(prefix string) (Container, bool) {
r.RLock()
defer r.RUnlock()
out := []interface{}{}
r.containers.WalkPrefix(prefix, func(_ string, v interface{}) bool {
out = append(out, v)
return false
})
if len(out) == 1 {
return out[0].(Container), true
}
return nil, false
}
// WalkImages runs f on every image of running containers the registry
@@ -369,10 +393,11 @@ func (r *registry) WalkImages(f func(*docker_client.APIImages)) {
defer r.RUnlock()
// Loop over containers so we only emit images for running containers.
for _, container := range r.containers {
image, ok := r.images[container.Image()]
r.containers.Walk(func(_ string, c interface{}) bool {
image, ok := r.images[c.(Container).Image()]
if ok {
f(image)
}
}
return false
})
}

View File

@@ -54,7 +54,7 @@ func (c *mockContainer) StartGatheringStats() error {
func (c *mockContainer) StopGatheringStats() {}
func (c *mockContainer) GetNode(_ []net.IP) report.Node {
func (c *mockContainer) GetNode() report.Node {
return report.MakeNodeWith(report.MakeContainerNodeID(c.c.ID), map[string]string{
docker.ContainerID: c.c.ID,
docker.ContainerName: c.c.Name,
@@ -64,6 +64,13 @@ func (c *mockContainer) GetNode(_ []net.IP) report.Node {
)
}
func (c *mockContainer) NetworkMode() (string, bool) {
return "", false
}
func (c *mockContainer) NetworkInfo([]net.IP) report.Sets {
return report.EmptySets
}
func (c *mockContainer) Container() *client.Container {
return c.c
}

View File

@@ -49,6 +49,57 @@ var (
ContainerImageTableTemplates = report.TableTemplates{
ImageLabelPrefix: {ID: ImageLabelPrefix, Label: "Docker Labels", Prefix: ImageLabelPrefix},
}
ContainerControls = []report.Control{
{
ID: AttachContainer,
Human: "Attach",
Icon: "fa-desktop",
Rank: 1,
},
{
ID: ExecContainer,
Human: "Exec shell",
Icon: "fa-terminal",
Rank: 2,
},
{
ID: StartContainer,
Human: "Start",
Icon: "fa-play",
Rank: 3,
},
{
ID: RestartContainer,
Human: "Restart",
Icon: "fa-repeat",
Rank: 4,
},
{
ID: PauseContainer,
Human: "Pause",
Icon: "fa-pause",
Rank: 5,
},
{
ID: UnpauseContainer,
Human: "Unpause",
Icon: "fa-play",
Rank: 6,
},
{
ID: StopContainer,
Human: "Stop",
Icon: "fa-stop",
Rank: 7,
},
{
ID: RemoveContainer,
Human: "Remove",
Icon: "fa-trash-o",
Rank: 8,
},
}
)
// Reporter generate Reports containing Container and ContainerImage topologies
@@ -96,66 +147,73 @@ func (r *Reporter) Report() (report.Report, error) {
return result, nil
}
func getLocalIPs() ([]string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil, err
}
ips := []string{}
for _, addr := range addrs {
// Not all addrs are IPNets.
if ipNet, ok := addr.(*net.IPNet); ok {
ips = append(ips, ipNet.IP.String())
}
}
return ips, nil
}
func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology {
result := report.MakeTopology().
WithMetadataTemplates(ContainerMetadataTemplates).
WithMetricTemplates(ContainerMetricTemplates).
WithTableTemplates(ContainerTableTemplates)
result.Controls.AddControl(report.Control{
ID: AttachContainer,
Human: "Attach",
Icon: "fa-desktop",
Rank: 1,
})
result.Controls.AddControl(report.Control{
ID: ExecContainer,
Human: "Exec shell",
Icon: "fa-terminal",
Rank: 2,
})
result.Controls.AddControl(report.Control{
ID: StartContainer,
Human: "Start",
Icon: "fa-play",
Rank: 3,
})
result.Controls.AddControl(report.Control{
ID: RestartContainer,
Human: "Restart",
Icon: "fa-repeat",
Rank: 4,
})
result.Controls.AddControl(report.Control{
ID: PauseContainer,
Human: "Pause",
Icon: "fa-pause",
Rank: 5,
})
result.Controls.AddControl(report.Control{
ID: UnpauseContainer,
Human: "Unpause",
Icon: "fa-play",
Rank: 6,
})
result.Controls.AddControl(report.Control{
ID: StopContainer,
Human: "Stop",
Icon: "fa-stop",
Rank: 7,
})
result.Controls.AddControl(report.Control{
ID: RemoveContainer,
Human: "Remove",
Icon: "fa-trash-o",
Rank: 8,
})
result.Controls.AddControls(ContainerControls)
metadata := map[string]string{report.ControlProbeID: r.probeID}
nodes := []report.Node{}
r.registry.WalkContainers(func(c Container) {
result.AddNode(c.GetNode(localAddrs).WithLatests(metadata))
nodes = append(nodes, c.GetNode().WithLatests(metadata))
})
// Copy the IP addresses from other containers where they share network
// namespaces & deal with containers in the host net namespace. This
// is recursive to deal with people who decide to be clever.
{
hostNetworkInfo := report.EmptySets
if hostIPs, err := getLocalIPs(); err == nil {
hostIPsWithScopes := addScopeToIPs(r.hostID, hostIPs)
hostNetworkInfo = hostNetworkInfo.
Add(ContainerIPs, report.MakeStringSet(hostIPs...)).
Add(ContainerIPsWithScopes, report.MakeStringSet(hostIPsWithScopes...))
}
var networkInfo func(prefix string) report.Sets
networkInfo = func(prefix string) report.Sets {
container, ok := r.registry.GetContainerByPrefix(prefix)
if !ok {
return report.EmptySets
}
networkMode, ok := container.NetworkMode()
if ok && strings.HasPrefix(networkMode, "container:") {
return networkInfo(networkMode[10:])
} else if ok && networkMode == NetworkModeHost {
return hostNetworkInfo
}
return container.NetworkInfo(localAddrs)
}
for _, node := range nodes {
id, ok := report.ParseContainerNodeID(node.ID)
if !ok {
continue
}
networkInfo := networkInfo(id)
result.AddNode(node.WithSets(networkInfo))
}
}
return result
}

View File

@@ -38,6 +38,8 @@ func (r *mockRegistry) WatchContainerUpdates(_ docker.ContainerUpdateWatcher) {}
func (r *mockRegistry) GetContainer(_ string) (docker.Container, bool) { return nil, false }
func (r *mockRegistry) GetContainerByPrefix(_ string) (docker.Container, bool) { return nil, false }
var (
mockRegistryInstance = &mockRegistry{
containersByPID: map[int]docker.Container{

View File

@@ -8,7 +8,6 @@ import (
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/endpoint"
"github.com/weaveworks/scope/probe/host"
"github.com/weaveworks/scope/report"
)
@@ -67,48 +66,6 @@ var ContainerRenderer = MakeFilter(
),
)
type containerWithHostIPsRenderer struct {
Renderer
}
// Render produces a process graph where the ips for host network mode are set
// to the host's IPs.
func (r containerWithHostIPsRenderer) Render(rpt report.Report, dct Decorator) report.Nodes {
containers := r.Renderer.Render(rpt, dct)
hosts := SelectHost.Render(rpt, dct)
outputs := report.Nodes{}
for id, c := range containers {
outputs[id] = c
networkMode, ok := c.Latest.Lookup(docker.ContainerNetworkMode)
if !ok || networkMode != docker.NetworkModeHost {
continue
}
h, ok := hosts[report.MakeHostNodeID(report.ExtractHostID(c))]
if !ok {
continue
}
newIPs := report.MakeStringSet()
hostNetworks, _ := h.Sets.Lookup(host.LocalNetworks)
for _, cidr := range hostNetworks {
if ip, _, err := net.ParseCIDR(cidr); err == nil {
newIPs = newIPs.Add(ip.String())
}
}
output := c.Copy()
output.Sets = c.Sets.Add(docker.ContainerIPs, newIPs)
outputs[id] = output
}
return outputs
}
// ContainerWithHostIPsRenderer is a Renderer which produces a container graph
// enriched with host IPs on containers where NetworkMode is Host
var ContainerWithHostIPsRenderer = containerWithHostIPsRenderer{ContainerRenderer}
type containerWithImageNameRenderer struct {
Renderer
}
@@ -140,7 +97,7 @@ func (r containerWithImageNameRenderer) Render(rpt report.Report, dct Decorator)
// ContainerWithImageNameRenderer is a Renderer which produces a container
// graph where the ranks are the image names, not their IDs
var ContainerWithImageNameRenderer = ApplyDecorators(containerWithImageNameRenderer{ContainerWithHostIPsRenderer})
var ContainerWithImageNameRenderer = ApplyDecorators(containerWithImageNameRenderer{ContainerRenderer})
// ContainerImageRenderer is a Renderer which produces a renderable container
// image graph by merging the container graph and the container image topology.

View File

@@ -69,28 +69,6 @@ func TestContainerFilterRenderer(t *testing.T) {
}
}
func TestContainerWithHostIPsRenderer(t *testing.T) {
input := fixture.Report.Copy()
input.Container.Nodes[fixture.ClientContainerNodeID] = input.Container.Nodes[fixture.ClientContainerNodeID].WithLatests(map[string]string{
docker.ContainerNetworkMode: "host",
})
nodes := render.ContainerWithHostIPsRenderer.Render(input, render.FilterNoop)
// Test host network nodes get the host IPs added.
haveNode, ok := nodes[fixture.ClientContainerNodeID]
if !ok {
t.Fatal("Expected output to have the client container node")
}
have, ok := haveNode.Sets.Lookup(docker.ContainerIPs)
if !ok {
t.Fatal("Container had no IPs set.")
}
want := report.MakeStringSet("10.10.10.0")
if !reflect.DeepEqual(want, have) {
t.Error(test.Diff(want, have))
}
}
func TestContainerHostnameRenderer(t *testing.T) {
have := Prune(render.ContainerHostnameRenderer.Render(fixture.Report, render.FilterNoop))
want := Prune(expected.RenderedContainerHostnames)

View File

@@ -37,11 +37,18 @@ func (cs Controls) Copy() Controls {
return result
}
// AddControl returns a fresh Controls, c added to cs.
// AddControl adds c added to cs.
func (cs Controls) AddControl(c Control) {
cs[c.ID] = c
}
// AddControls adds a collection of controls to cs.
func (cs Controls) AddControls(controls []Control) {
for _, c := range controls {
cs[c.ID] = c
}
}
// NodeControls represent the individual controls that are valid for a given
// node at a given point in time. Its is immutable. A zero-value for Timestamp
// indicated this NodeControls is 'not set'.

20
vendor/github.com/armon/go-radix/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Armon Dadgar
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

496
vendor/github.com/armon/go-radix/radix.go generated vendored Normal file
View File

@@ -0,0 +1,496 @@
package radix
import (
"sort"
"strings"
)
// WalkFn is used when walking the tree. Takes a
// key and value, returning if iteration should
// be terminated.
type WalkFn func(s string, v interface{}) bool
// leafNode is used to represent a value
type leafNode struct {
key string
val interface{}
}
// edge is used to represent an edge node
type edge struct {
label byte
node *node
}
type node struct {
// leaf is used to store possible leaf
leaf *leafNode
// prefix is the common prefix we ignore
prefix string
// Edges should be stored in-order for iteration.
// We avoid a fully materialized slice to save memory,
// since in most cases we expect to be sparse
edges edges
}
func (n *node) isLeaf() bool {
return n.leaf != nil
}
func (n *node) addEdge(e edge) {
n.edges = append(n.edges, e)
n.edges.Sort()
}
func (n *node) replaceEdge(e edge) {
num := len(n.edges)
idx := sort.Search(num, func(i int) bool {
return n.edges[i].label >= e.label
})
if idx < num && n.edges[idx].label == e.label {
n.edges[idx].node = e.node
return
}
panic("replacing missing edge")
}
func (n *node) getEdge(label byte) *node {
num := len(n.edges)
idx := sort.Search(num, func(i int) bool {
return n.edges[i].label >= label
})
if idx < num && n.edges[idx].label == label {
return n.edges[idx].node
}
return nil
}
func (n *node) delEdge(label byte) {
num := len(n.edges)
idx := sort.Search(num, func(i int) bool {
return n.edges[i].label >= label
})
if idx < num && n.edges[idx].label == label {
copy(n.edges[idx:], n.edges[idx+1:])
n.edges[len(n.edges)-1] = edge{}
n.edges = n.edges[:len(n.edges)-1]
}
}
type edges []edge
func (e edges) Len() int {
return len(e)
}
func (e edges) Less(i, j int) bool {
return e[i].label < e[j].label
}
func (e edges) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
func (e edges) Sort() {
sort.Sort(e)
}
// Tree implements a radix tree. This can be treated as a
// Dictionary abstract data type. The main advantage over
// a standard hash map is prefix-based lookups and
// ordered iteration,
type Tree struct {
root *node
size int
}
// New returns an empty Tree
func New() *Tree {
return NewFromMap(nil)
}
// NewFromMap returns a new tree containing the keys
// from an existing map
func NewFromMap(m map[string]interface{}) *Tree {
t := &Tree{root: &node{}}
for k, v := range m {
t.Insert(k, v)
}
return t
}
// Len is used to return the number of elements in the tree
func (t *Tree) Len() int {
return t.size
}
// longestPrefix finds the length of the shared prefix
// of two strings
func longestPrefix(k1, k2 string) int {
max := len(k1)
if l := len(k2); l < max {
max = l
}
var i int
for i = 0; i < max; i++ {
if k1[i] != k2[i] {
break
}
}
return i
}
// Insert is used to add a newentry or update
// an existing entry. Returns if updated.
func (t *Tree) Insert(s string, v interface{}) (interface{}, bool) {
var parent *node
n := t.root
search := s
for {
// Handle key exhaution
if len(search) == 0 {
if n.isLeaf() {
old := n.leaf.val
n.leaf.val = v
return old, true
}
n.leaf = &leafNode{
key: s,
val: v,
}
t.size++
return nil, false
}
// Look for the edge
parent = n
n = n.getEdge(search[0])
// No edge, create one
if n == nil {
e := edge{
label: search[0],
node: &node{
leaf: &leafNode{
key: s,
val: v,
},
prefix: search,
},
}
parent.addEdge(e)
t.size++
return nil, false
}
// Determine longest prefix of the search key on match
commonPrefix := longestPrefix(search, n.prefix)
if commonPrefix == len(n.prefix) {
search = search[commonPrefix:]
continue
}
// Split the node
t.size++
child := &node{
prefix: search[:commonPrefix],
}
parent.replaceEdge(edge{
label: search[0],
node: child,
})
// Restore the existing node
child.addEdge(edge{
label: n.prefix[commonPrefix],
node: n,
})
n.prefix = n.prefix[commonPrefix:]
// Create a new leaf node
leaf := &leafNode{
key: s,
val: v,
}
// If the new key is a subset, add to to this node
search = search[commonPrefix:]
if len(search) == 0 {
child.leaf = leaf
return nil, false
}
// Create a new edge for the node
child.addEdge(edge{
label: search[0],
node: &node{
leaf: leaf,
prefix: search,
},
})
return nil, false
}
}
// Delete is used to delete a key, returning the previous
// value and if it was deleted
func (t *Tree) Delete(s string) (interface{}, bool) {
var parent *node
var label byte
n := t.root
search := s
for {
// Check for key exhaution
if len(search) == 0 {
if !n.isLeaf() {
break
}
goto DELETE
}
// Look for an edge
parent = n
label = search[0]
n = n.getEdge(label)
if n == nil {
break
}
// Consume the search prefix
if strings.HasPrefix(search, n.prefix) {
search = search[len(n.prefix):]
} else {
break
}
}
return nil, false
DELETE:
// Delete the leaf
leaf := n.leaf
n.leaf = nil
t.size--
// Check if we should delete this node from the parent
if parent != nil && len(n.edges) == 0 {
parent.delEdge(label)
}
// Check if we should merge this node
if n != t.root && len(n.edges) == 1 {
n.mergeChild()
}
// Check if we should merge the parent's other child
if parent != nil && parent != t.root && len(parent.edges) == 1 && !parent.isLeaf() {
parent.mergeChild()
}
return leaf.val, true
}
func (n *node) mergeChild() {
e := n.edges[0]
child := e.node
n.prefix = n.prefix + child.prefix
n.leaf = child.leaf
n.edges = child.edges
}
// Get is used to lookup a specific key, returning
// the value and if it was found
func (t *Tree) Get(s string) (interface{}, bool) {
n := t.root
search := s
for {
// Check for key exhaution
if len(search) == 0 {
if n.isLeaf() {
return n.leaf.val, true
}
break
}
// Look for an edge
n = n.getEdge(search[0])
if n == nil {
break
}
// Consume the search prefix
if strings.HasPrefix(search, n.prefix) {
search = search[len(n.prefix):]
} else {
break
}
}
return nil, false
}
// LongestPrefix is like Get, but instead of an
// exact match, it will return the longest prefix match.
func (t *Tree) LongestPrefix(s string) (string, interface{}, bool) {
var last *leafNode
n := t.root
search := s
for {
// Look for a leaf node
if n.isLeaf() {
last = n.leaf
}
// Check for key exhaution
if len(search) == 0 {
break
}
// Look for an edge
n = n.getEdge(search[0])
if n == nil {
break
}
// Consume the search prefix
if strings.HasPrefix(search, n.prefix) {
search = search[len(n.prefix):]
} else {
break
}
}
if last != nil {
return last.key, last.val, true
}
return "", nil, false
}
// Minimum is used to return the minimum value in the tree
func (t *Tree) Minimum() (string, interface{}, bool) {
n := t.root
for {
if n.isLeaf() {
return n.leaf.key, n.leaf.val, true
}
if len(n.edges) > 0 {
n = n.edges[0].node
} else {
break
}
}
return "", nil, false
}
// Maximum is used to return the maximum value in the tree
func (t *Tree) Maximum() (string, interface{}, bool) {
n := t.root
for {
if num := len(n.edges); num > 0 {
n = n.edges[num-1].node
continue
}
if n.isLeaf() {
return n.leaf.key, n.leaf.val, true
}
break
}
return "", nil, false
}
// Walk is used to walk the tree
func (t *Tree) Walk(fn WalkFn) {
recursiveWalk(t.root, fn)
}
// WalkPrefix is used to walk the tree under a prefix
func (t *Tree) WalkPrefix(prefix string, fn WalkFn) {
n := t.root
search := prefix
for {
// Check for key exhaution
if len(search) == 0 {
recursiveWalk(n, fn)
return
}
// Look for an edge
n = n.getEdge(search[0])
if n == nil {
break
}
// Consume the search prefix
if strings.HasPrefix(search, n.prefix) {
search = search[len(n.prefix):]
} else if strings.HasPrefix(n.prefix, search) {
// Child may be under our search prefix
recursiveWalk(n, fn)
return
} else {
break
}
}
}
// WalkPath is used to walk the tree, but only visiting nodes
// from the root down to a given leaf. Where WalkPrefix walks
// all the entries *under* the given prefix, this walks the
// entries *above* the given prefix.
func (t *Tree) WalkPath(path string, fn WalkFn) {
n := t.root
search := path
for {
// Visit the leaf values if any
if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
return
}
// Check for key exhaution
if len(search) == 0 {
return
}
// Look for an edge
n = n.getEdge(search[0])
if n == nil {
return
}
// Consume the search prefix
if strings.HasPrefix(search, n.prefix) {
search = search[len(n.prefix):]
} else {
break
}
}
}
// recursiveWalk is used to do a pre-order walk of a node
// recursively. Returns true if the walk should be aborted
func recursiveWalk(n *node, fn WalkFn) bool {
// Visit the leaf values if any
if n.leaf != nil && fn(n.leaf.key, n.leaf.val) {
return true
}
// Recurse on the children
for _, e := range n.edges {
if recursiveWalk(e.node, fn) {
return true
}
}
return false
}
// ToMap is used to walk the tree and convert it into a map
func (t *Tree) ToMap() map[string]interface{} {
out := make(map[string]interface{}, t.size)
t.Walk(func(k string, v interface{}) bool {
out[k] = v
return false
})
return out
}

157
vendor/manifest vendored

File diff suppressed because it is too large Load Diff