1package container // import "github.com/docker/docker/daemon/cluster/executor/container"
2
3import (
4	"testing"
5
6	"context"
7	"time"
8
9	"github.com/docker/docker/daemon"
10	"github.com/docker/swarmkit/api"
11)
12
13// TestWaitNodeAttachment tests that the waitNodeAttachment method successfully
14// blocks until the required node attachment becomes available.
15func TestWaitNodeAttachment(t *testing.T) {
16	emptyDaemon := &daemon.Daemon{}
17
18	// the daemon creates an attachment store as an object, which means it's
19	// initialized to an empty store by default. get that attachment store here
20	// and add some attachments to it
21	attachmentStore := emptyDaemon.GetAttachmentStore()
22
23	// create a set of attachments to put into the attahcment store
24	attachments := map[string]string{
25		"network1": "10.1.2.3/24",
26	}
27
28	// this shouldn't fail, but check it anyway just in case
29	err := attachmentStore.ResetAttachments(attachments)
30	if err != nil {
31		t.Fatalf("error resetting attachments: %v", err)
32	}
33
34	// create a containerConfig to put in the adapter. we don't need the task,
35	// actually; only the networkAttachments are needed.
36	container := &containerConfig{
37		task: nil,
38		networksAttachments: map[string]*api.NetworkAttachment{
39			// network1 is already present in the attachment store.
40			"network1": {
41				Network: &api.Network{
42					ID: "network1",
43					DriverState: &api.Driver{
44						Name: "overlay",
45					},
46				},
47			},
48			// network2 is not yet present in the attachment store, and we
49			// should block while waiting for it.
50			"network2": {
51				Network: &api.Network{
52					ID: "network2",
53					DriverState: &api.Driver{
54						Name: "overlay",
55					},
56				},
57			},
58			// localnetwork is not and will never be in the attachment store,
59			// but we should not block on it, because it is not an overlay
60			// network
61			"localnetwork": {
62				Network: &api.Network{
63					ID: "localnetwork",
64					DriverState: &api.Driver{
65						Name: "bridge",
66					},
67				},
68			},
69		},
70	}
71
72	// we don't create an adapter using the newContainerAdapter package,
73	// because it does a bunch of checks and validations. instead, create one
74	// "from scratch" so we only have the fields we need.
75	adapter := &containerAdapter{
76		backend:   emptyDaemon,
77		container: container,
78	}
79
80	// create a context to do call the method with
81	ctx, cancel := context.WithCancel(context.Background())
82	defer cancel()
83
84	// create a channel to allow the goroutine that we run the method call in
85	// to signal that it's done.
86	doneChan := make(chan struct{})
87
88	// store the error return value of waitNodeAttachments in this variable
89	var waitNodeAttachmentsErr error
90	// NOTE(dperny): be careful running goroutines in test code. if a test
91	// terminates with ie t.Fatalf or a failed requirement, runtime.Goexit gets
92	// called, which does run defers but does not clean up child goroutines.
93	// we defer canceling the context here, which should stop this goroutine
94	// from leaking
95	go func() {
96		waitNodeAttachmentsErr = adapter.waitNodeAttachments(ctx)
97		// signal that we've completed
98		close(doneChan)
99	}()
100
101	// wait 200ms to allow the waitNodeAttachments call to spin for a bit
102	time.Sleep(200 * time.Millisecond)
103	select {
104	case <-doneChan:
105		if waitNodeAttachmentsErr == nil {
106			t.Fatalf("waitNodeAttachments exited early with no error")
107		} else {
108			t.Fatalf(
109				"waitNodeAttachments exited early with an error: %v",
110				waitNodeAttachmentsErr,
111			)
112		}
113	default:
114		// allow falling through; this is the desired case
115	}
116
117	// now update the node attachments to include another network attachment
118	attachments["network2"] = "10.3.4.5/24"
119	err = attachmentStore.ResetAttachments(attachments)
120	if err != nil {
121		t.Fatalf("error resetting attachments: %v", err)
122	}
123
124	// now wait 200 ms for waitNodeAttachments to pick up the change
125	time.Sleep(200 * time.Millisecond)
126
127	// and check that waitNodeAttachments has exited with no error
128	select {
129	case <-doneChan:
130		if waitNodeAttachmentsErr != nil {
131			t.Fatalf(
132				"waitNodeAttachments returned an error: %v",
133				waitNodeAttachmentsErr,
134			)
135		}
136	default:
137		t.Fatalf("waitNodeAttachments did not exit yet, but should have")
138	}
139}
140