Files
weave-scope/probe/docker/registry_test.go
Krzesimir Nowak 41193b428e Make control handlers registry an object and extend its functionality
It is not a singleton anymore. Instead it is an object with a registry
backend. The default registry backend is provided, which is equivalent
to what used to be before. Custom backend can be provided for testing
purposes.

The registry also supports batch operations to remove and add handlers
as an atomic step.
2016-08-12 17:03:42 +02:00

513 lines
12 KiB
Go

package docker_test
import (
"fmt"
"net"
"runtime"
"sort"
"sync"
"testing"
"time"
client "github.com/fsouza/go-dockerclient"
"github.com/weaveworks/scope/common/mtime"
"github.com/weaveworks/scope/probe/controls"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/report"
"github.com/weaveworks/scope/test"
"github.com/weaveworks/scope/test/reflect"
)
func testRegistry() docker.Registry {
hr := controls.NewDefaultHandlerRegistry()
registry, _ := docker.NewRegistry(10*time.Second, nil, true, "", hr)
return registry
}
type mockContainer struct {
c *client.Container
}
func (c *mockContainer) UpdateState(_ *client.Container) {}
func (c *mockContainer) ID() string {
return c.c.ID
}
func (c *mockContainer) PID() int {
return c.c.State.Pid
}
func (c *mockContainer) Image() string {
return c.c.Image
}
func (c *mockContainer) Hostname() string {
return ""
}
func (c *mockContainer) State() string {
return "Up 3 minutes"
}
func (c *mockContainer) StateString() string {
return docker.StateRunning
}
func (c *mockContainer) StartGatheringStats() error {
return nil
}
func (c *mockContainer) StopGatheringStats() {}
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,
docker.ImageID: c.c.Image,
}).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID(c.c.Image))),
)
}
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
}
func (c *mockContainer) HasTTY() bool { return true }
type mockDockerClient struct {
sync.RWMutex
apiContainers []client.APIContainers
containers map[string]*client.Container
apiImages []client.APIImages
networks []client.Network
events []chan<- *client.APIEvents
}
func (m *mockDockerClient) ListContainers(client.ListContainersOptions) ([]client.APIContainers, error) {
m.RLock()
defer m.RUnlock()
return m.apiContainers, nil
}
func (m *mockDockerClient) InspectContainer(id string) (*client.Container, error) {
m.RLock()
defer m.RUnlock()
c, ok := m.containers[id]
if !ok {
return nil, &client.NoSuchContainer{}
}
return c, nil
}
func (m *mockDockerClient) ListImages(client.ListImagesOptions) ([]client.APIImages, error) {
m.RLock()
defer m.RUnlock()
return m.apiImages, nil
}
func (m *mockDockerClient) ListNetworks() ([]client.Network, error) {
m.RLock()
defer m.RUnlock()
return m.networks, nil
}
func (m *mockDockerClient) AddEventListener(events chan<- *client.APIEvents) error {
m.Lock()
defer m.Unlock()
m.events = append(m.events, events)
return nil
}
func (m *mockDockerClient) RemoveEventListener(events chan *client.APIEvents) error {
m.Lock()
defer m.Unlock()
for i, c := range m.events {
if c == events {
m.events = append(m.events[:i], m.events[i+1:]...)
}
}
return nil
}
func (m *mockDockerClient) StartContainer(_ string, _ *client.HostConfig) error {
return fmt.Errorf("started")
}
func (m *mockDockerClient) StopContainer(_ string, _ uint) error {
return fmt.Errorf("stopped")
}
func (m *mockDockerClient) RestartContainer(_ string, _ uint) error {
return fmt.Errorf("restarted")
}
func (m *mockDockerClient) PauseContainer(_ string) error {
return fmt.Errorf("paused")
}
func (m *mockDockerClient) UnpauseContainer(_ string) error {
return fmt.Errorf("unpaused")
}
func (m *mockDockerClient) RemoveContainer(_ client.RemoveContainerOptions) error {
return fmt.Errorf("remove")
}
type mockCloseWaiter struct{}
func (mockCloseWaiter) Close() error { return nil }
func (mockCloseWaiter) Wait() error { return nil }
func (m *mockDockerClient) AttachToContainerNonBlocking(_ client.AttachToContainerOptions) (client.CloseWaiter, error) {
return mockCloseWaiter{}, nil
}
func (m *mockDockerClient) CreateExec(client.CreateExecOptions) (*client.Exec, error) {
return &client.Exec{ID: "id"}, nil
}
func (m *mockDockerClient) StartExecNonBlocking(string, client.StartExecOptions) (client.CloseWaiter, error) {
return mockCloseWaiter{}, nil
}
func (m *mockDockerClient) send(event *client.APIEvents) {
m.RLock()
defer m.RUnlock()
for _, c := range m.events {
c <- event
}
}
var (
startTime = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
container1 = &client.Container{
ID: "ping",
Name: "pong",
Image: "baz",
State: client.State{
Pid: 2,
Running: true,
StartedAt: startTime,
},
NetworkSettings: &client.NetworkSettings{
IPAddress: "1.2.3.4",
Ports: map[client.Port][]client.PortBinding{
client.Port("80/tcp"): {
{
HostIP: "1.2.3.4",
HostPort: "80",
},
},
client.Port("81/tcp"): {},
},
Networks: map[string]client.ContainerNetwork{
"network1": {
IPAddress: "5.6.7.8",
},
},
},
Config: &client.Config{
Labels: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
}
container2 = &client.Container{
ID: "wiff",
Name: "waff",
Image: "baz",
State: client.State{Pid: 1, Running: true},
Config: &client.Config{
Labels: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
}
renamedContainer = &client.Container{
ID: "renamed",
Name: "renamed",
Image: "baz",
State: client.State{Pid: 1, Running: true},
Config: &client.Config{
Labels: map[string]string{
"foo1": "bar1",
"foo2": "bar2",
},
},
}
apiContainer1 = client.APIContainers{ID: "ping"}
apiContainer2 = client.APIContainers{ID: "wiff"}
renamedAPIContainer = client.APIContainers{ID: "renamed"}
apiImage1 = client.APIImages{
ID: "baz",
RepoTags: []string{"bang", "not-chosen"},
Labels: map[string]string{
"imgfoo1": "bar1",
"imgfoo2": "bar2",
},
}
network1 = client.Network{
ID: "deadbeef",
Name: "network1",
Scope: "local",
IPAM: client.IPAMOptions{
Config: []client.IPAMConfig{{Subnet: "5.6.7.8/24"}},
},
}
)
func newMockClient() *mockDockerClient {
return &mockDockerClient{
apiContainers: []client.APIContainers{apiContainer1},
containers: map[string]*client.Container{"ping": container1},
apiImages: []client.APIImages{apiImage1},
networks: []client.Network{network1},
}
}
func setupStubs(mdc *mockDockerClient, f func()) {
oldDockerClient, oldNewContainer := docker.NewDockerClientStub, docker.NewContainerStub
defer func() { docker.NewDockerClientStub, docker.NewContainerStub = oldDockerClient, oldNewContainer }()
docker.NewDockerClientStub = func(endpoint string) (docker.Client, error) {
return mdc, nil
}
docker.NewContainerStub = func(c *client.Container, _ string) docker.Container {
return &mockContainer{c}
}
f()
}
type containers []docker.Container
func (c containers) Len() int { return len(c) }
func (c containers) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c containers) Less(i, j int) bool { return c[i].ID() < c[j].ID() }
func allContainers(r docker.Registry) []docker.Container {
result := []docker.Container{}
r.WalkContainers(func(c docker.Container) {
result = append(result, c)
})
sort.Sort(containers(result))
return result
}
func allImages(r docker.Registry) []client.APIImages {
result := []client.APIImages{}
r.WalkImages(func(i client.APIImages) {
result = append(result, i)
})
return result
}
func allNetworks(r docker.Registry) []client.Network {
result := []client.Network{}
r.WalkNetworks(func(i client.Network) {
result = append(result, i)
})
return result
}
func TestRegistry(t *testing.T) {
mdc := newMockClient()
setupStubs(mdc, func() {
registry := testRegistry()
defer registry.Stop()
runtime.Gosched()
{
want := []docker.Container{&mockContainer{container1}}
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
return allContainers(registry)
})
}
{
want := []client.APIImages{apiImage1}
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
return allImages(registry)
})
}
{
want := []client.Network{network1}
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
return allNetworks(registry)
})
}
})
}
func TestLookupByPID(t *testing.T) {
mdc := newMockClient()
setupStubs(mdc, func() {
registry := testRegistry()
defer registry.Stop()
want := docker.Container(&mockContainer{container1})
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
var have docker.Container
registry.LockedPIDLookup(func(lookup func(int) docker.Container) {
have = lookup(2)
})
return have
})
})
}
func TestRegistryEvents(t *testing.T) {
mdc := newMockClient()
setupStubs(mdc, func() {
registry := testRegistry()
defer registry.Stop()
runtime.Gosched()
check := func(want []docker.Container) {
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
return allContainers(registry)
})
}
{
mdc.Lock()
mdc.apiContainers = []client.APIContainers{apiContainer1, apiContainer2}
mdc.containers["wiff"] = container2
mdc.Unlock()
mdc.send(&client.APIEvents{Status: docker.StartEvent, ID: "wiff"})
runtime.Gosched()
want := []docker.Container{&mockContainer{container1}, &mockContainer{container2}}
check(want)
}
{
mdc.Lock()
mdc.apiContainers = []client.APIContainers{apiContainer1}
delete(mdc.containers, "wiff")
mdc.Unlock()
mdc.send(&client.APIEvents{Status: docker.DestroyEvent, ID: "wiff"})
runtime.Gosched()
want := []docker.Container{&mockContainer{container1}}
check(want)
}
{
mdc.Lock()
mdc.apiContainers = []client.APIContainers{}
delete(mdc.containers, "ping")
mdc.Unlock()
mdc.send(&client.APIEvents{Status: docker.DieEvent, ID: "ping"})
runtime.Gosched()
want := []docker.Container{}
check(want)
}
{
mdc.send(&client.APIEvents{Status: docker.DieEvent, ID: "doesntexist"})
runtime.Gosched()
want := []docker.Container{}
check(want)
}
{
mdc.Lock()
mdc.apiContainers = []client.APIContainers{renamedAPIContainer}
mdc.containers[renamedContainer.ID] = renamedContainer
mdc.Unlock()
mdc.send(&client.APIEvents{Status: docker.RenameEvent, ID: renamedContainer.ID})
runtime.Gosched()
want := []docker.Container{&mockContainer{renamedContainer}}
check(want)
}
})
}
func TestRegistryDelete(t *testing.T) {
mtime.NowForce(mtime.Now())
defer mtime.NowReset()
mdc := newMockClient()
setupStubs(mdc, func() {
registry := testRegistry()
defer registry.Stop()
runtime.Gosched()
// Collect all the events.
mtx := sync.Mutex{}
nodes := []report.Node{}
registry.WatchContainerUpdates(func(n report.Node) {
mtx.Lock()
defer mtx.Unlock()
nodes = append(nodes, n)
})
check := func(want []docker.Container) {
test.Poll(t, 100*time.Millisecond, want, func() interface{} {
return allContainers(registry)
})
}
want := []docker.Container{&mockContainer{container1}}
check(want)
{
mdc.Lock()
mdc.apiContainers = []client.APIContainers{}
delete(mdc.containers, "ping")
mdc.Unlock()
mdc.send(&client.APIEvents{Status: docker.DestroyEvent, ID: "ping"})
runtime.Gosched()
check([]docker.Container{})
mtx.Lock()
want := []report.Node{
report.MakeNodeWith(report.MakeContainerNodeID("ping"), map[string]string{
docker.ContainerID: "ping",
docker.ContainerState: "deleted",
}),
}
if !reflect.DeepEqual(want, nodes) {
t.Errorf("Didn't get right container updates: %v", test.Diff(want, nodes))
}
nodes = []report.Node{}
mtx.Unlock()
}
})
}
func TestDockerImageName(t *testing.T) {
for _, input := range []struct{ in, name string }{
{"foo/bar", "foo/bar"},
{"foo/bar:baz", "foo/bar"},
{"reg:123/foo/bar:baz", "foo/bar"},
{"docker-registry.domain.name:5000/repo/image1:ver", "repo/image1"},
{"foo", "foo"},
} {
name := docker.ImageNameWithoutVersion(input.in)
if name != input.name {
t.Fatalf("%s: %s != %s", input.in, name, input.name)
}
}
}