1// Copyright 2014 go-dockerclient authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package docker
6
7import (
8	"bufio"
9	"crypto/tls"
10	"crypto/x509"
11	"io/ioutil"
12	"net/http"
13	"net/http/httptest"
14	"strings"
15	"testing"
16	"time"
17
18	"github.com/google/go-cmp/cmp"
19)
20
21func TestEventListeners(t *testing.T) {
22	t.Parallel()
23	testEventListeners("TestEventListeners", t, httptest.NewServer, NewClient)
24}
25
26func TestTLSEventListeners(t *testing.T) {
27	t.Parallel()
28	testEventListeners("TestTLSEventListeners", t, func(handler http.Handler) *httptest.Server {
29		server := httptest.NewUnstartedServer(handler)
30
31		cert, err := tls.LoadX509KeyPair("testing/data/server.pem", "testing/data/serverkey.pem")
32		if err != nil {
33			t.Fatalf("Error loading server key pair: %s", err)
34		}
35
36		caCert, err := ioutil.ReadFile("testing/data/ca.pem")
37		if err != nil {
38			t.Fatalf("Error loading ca certificate: %s", err)
39		}
40		caPool := x509.NewCertPool()
41		if !caPool.AppendCertsFromPEM(caCert) {
42			t.Fatalf("Could not add ca certificate")
43		}
44
45		server.TLS = &tls.Config{
46			Certificates: []tls.Certificate{cert},
47			RootCAs:      caPool,
48		}
49		server.StartTLS()
50		return server
51	}, func(url string) (*Client, error) {
52		return NewTLSClient(url, "testing/data/cert.pem", "testing/data/key.pem", "testing/data/ca.pem")
53	})
54}
55
56func testEventListeners(testName string, t *testing.T, buildServer func(http.Handler) *httptest.Server, buildClient func(string) (*Client, error)) {
57	response := `{"action":"pull","type":"image","actor":{"id":"busybox:latest","attributes":{}},"time":1442421700,"timeNano":1442421700598988358}
58{"action":"create","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716853979870}
59{"action":"attach","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716894759198}
60{"action":"start","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716983607193}
61{"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
62{"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
63{"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966}
64{"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970}
65{"Action":"create","Actor":{"Attributes":{"HAProxyMode":"http","HealthCheck":"HttpGet","HealthCheckArgs":"http://127.0.0.1:39051/status/check","ServicePort_8080":"17801","image":"datanerd.us/siteeng/sample-app-go:latest","name":"sample-app-client-go-69818c1223ddb5"},"ID":"a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16"},"Type":"container","from":"datanerd.us/siteeng/sample-app-go:latest","id":"a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16","status":"create","time":1459133932,"timeNano":1459133932961735842}`
66
67	server := buildServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
68		rsc := bufio.NewScanner(strings.NewReader(response))
69		for rsc.Scan() {
70			w.Write(rsc.Bytes())
71			w.(http.Flusher).Flush()
72			time.Sleep(10 * time.Millisecond)
73		}
74	}))
75	defer server.Close()
76
77	wantedEvents := []APIEvents{
78		{
79			Action: "pull",
80			Type:   "image",
81			Actor: APIActor{
82				ID:         "busybox:latest",
83				Attributes: map[string]string{},
84			},
85
86			Status: "pull",
87			ID:     "busybox:latest",
88
89			Time:     1442421700,
90			TimeNano: 1442421700598988358,
91		},
92		{
93			Action: "create",
94			Type:   "container",
95			Actor: APIActor{
96				ID: "5745704abe9caa5",
97				Attributes: map[string]string{
98					"image": "busybox",
99				},
100			},
101
102			Status: "create",
103			ID:     "5745704abe9caa5",
104			From:   "busybox",
105
106			Time:     1442421716,
107			TimeNano: 1442421716853979870,
108		},
109		{
110			Action: "attach",
111			Type:   "container",
112			Actor: APIActor{
113				ID: "5745704abe9caa5",
114				Attributes: map[string]string{
115					"image": "busybox",
116				},
117			},
118
119			Status: "attach",
120			ID:     "5745704abe9caa5",
121			From:   "busybox",
122
123			Time:     1442421716,
124			TimeNano: 1442421716894759198,
125		},
126		{
127			Action: "start",
128			Type:   "container",
129			Actor: APIActor{
130				ID: "5745704abe9caa5",
131				Attributes: map[string]string{
132					"image": "busybox",
133				},
134			},
135
136			Status: "start",
137			ID:     "5745704abe9caa5",
138			From:   "busybox",
139
140			Time:     1442421716,
141			TimeNano: 1442421716983607193,
142		},
143
144		{
145			Action: "create",
146			Type:   "container",
147			Actor: APIActor{
148				ID: "dfdf82bd3881",
149				Attributes: map[string]string{
150					"image": "base:latest",
151				},
152			},
153
154			Status: "create",
155			ID:     "dfdf82bd3881",
156			From:   "base:latest",
157
158			Time: 1374067924,
159		},
160		{
161			Action: "start",
162			Type:   "container",
163			Actor: APIActor{
164				ID: "dfdf82bd3881",
165				Attributes: map[string]string{
166					"image": "base:latest",
167				},
168			},
169
170			Status: "start",
171			ID:     "dfdf82bd3881",
172			From:   "base:latest",
173
174			Time: 1374067924,
175		},
176		{
177			Action: "stop",
178			Type:   "container",
179			Actor: APIActor{
180				ID: "dfdf82bd3881",
181				Attributes: map[string]string{
182					"image": "base:latest",
183				},
184			},
185
186			Status: "stop",
187			ID:     "dfdf82bd3881",
188			From:   "base:latest",
189
190			Time: 1374067966,
191		},
192		{
193			Action: "destroy",
194			Type:   "container",
195			Actor: APIActor{
196				ID: "dfdf82bd3881",
197				Attributes: map[string]string{
198					"image": "base:latest",
199				},
200			},
201
202			Status: "destroy",
203			ID:     "dfdf82bd3881",
204			From:   "base:latest",
205
206			Time: 1374067970,
207		},
208		{
209			Action:   "create",
210			Type:     "container",
211			Status:   "create",
212			From:     "datanerd.us/siteeng/sample-app-go:latest",
213			ID:       "a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16",
214			Time:     1459133932,
215			TimeNano: 1459133932961735842,
216			Actor: APIActor{
217				ID: "a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16",
218				Attributes: map[string]string{
219					"HAProxyMode":      "http",
220					"HealthCheck":      "HttpGet",
221					"HealthCheckArgs":  "http://127.0.0.1:39051/status/check",
222					"ServicePort_8080": "17801",
223					"image":            "datanerd.us/siteeng/sample-app-go:latest",
224					"name":             "sample-app-client-go-69818c1223ddb5",
225				},
226			},
227		},
228	}
229
230	client, err := buildClient(server.URL)
231	if err != nil {
232		t.Errorf("Failed to create client: %s", err)
233	}
234	client.SkipServerVersionCheck = true
235
236	listener := make(chan *APIEvents, len(wantedEvents)+1)
237	defer func() {
238		if err = client.RemoveEventListener(listener); err != nil {
239			t.Error(err)
240		}
241	}()
242
243	err = client.AddEventListener(listener)
244	if err != nil {
245		t.Errorf("Failed to add event listener: %s", err)
246	}
247
248	timeout := time.After(5 * time.Second)
249	events := make([]APIEvents, 0, len(wantedEvents))
250
251loop:
252	for i := range wantedEvents {
253		select {
254		case msg, ok := <-listener:
255			if !ok {
256				break loop
257			}
258			events = append(events, *msg)
259		case <-timeout:
260			t.Fatalf("%s: timed out waiting on events after %d events", testName, i)
261		}
262	}
263	cmpr := cmp.Comparer(func(e1, e2 APIEvents) bool {
264		return e1.Action == e2.Action && e1.Actor.ID == e2.Actor.ID
265	})
266	if dff := cmp.Diff(events, wantedEvents, cmpr); dff != "" {
267		t.Errorf("wrong events:\n%s", dff)
268	}
269}
270
271func TestEventListenerReAdding(t *testing.T) {
272	t.Parallel()
273	endChan := make(chan bool)
274	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
275		<-endChan
276	}))
277
278	client, err := NewClient(server.URL)
279	if err != nil {
280		t.Errorf("Failed to create client: %s", err)
281	}
282
283	listener := make(chan *APIEvents, 10)
284	if err := client.AddEventListener(listener); err != nil {
285		t.Errorf("Failed to add event listener: %s", err)
286	}
287
288	// Make sure eventHijack() is started with the current eventMonitoringState.
289	time.Sleep(10 * time.Millisecond)
290
291	if err := client.RemoveEventListener(listener); err != nil {
292		t.Errorf("Failed to remove event listener: %s", err)
293	}
294
295	if err := client.AddEventListener(listener); err != nil {
296		t.Errorf("Failed to add event listener: %s", err)
297	}
298
299	endChan <- true
300
301	// Give the goroutine of the first eventHijack() time to handle the EOF.
302	time.Sleep(10 * time.Millisecond)
303}
304