1package kv // import "github.com/docker/docker/pkg/discovery/kv"
2
3import (
4	"errors"
5	"io/ioutil"
6	"os"
7	"path"
8	"testing"
9	"time"
10
11	"github.com/docker/docker/pkg/discovery"
12	"github.com/docker/libkv"
13	"github.com/docker/libkv/store"
14	"github.com/go-check/check"
15)
16
17// Hook up gocheck into the "go test" runner.
18func Test(t *testing.T) { check.TestingT(t) }
19
20type DiscoverySuite struct{}
21
22var _ = check.Suite(&DiscoverySuite{})
23
24func (ds *DiscoverySuite) TestInitialize(c *check.C) {
25	storeMock := &FakeStore{
26		Endpoints: []string{"127.0.0.1"},
27	}
28	d := &Discovery{backend: store.CONSUL}
29	d.Initialize("127.0.0.1", 0, 0, nil)
30	d.store = storeMock
31
32	s := d.store.(*FakeStore)
33	c.Assert(s.Endpoints, check.HasLen, 1)
34	c.Assert(s.Endpoints[0], check.Equals, "127.0.0.1")
35	c.Assert(d.path, check.Equals, defaultDiscoveryPath)
36
37	storeMock = &FakeStore{
38		Endpoints: []string{"127.0.0.1:1234"},
39	}
40	d = &Discovery{backend: store.CONSUL}
41	d.Initialize("127.0.0.1:1234/path", 0, 0, nil)
42	d.store = storeMock
43
44	s = d.store.(*FakeStore)
45	c.Assert(s.Endpoints, check.HasLen, 1)
46	c.Assert(s.Endpoints[0], check.Equals, "127.0.0.1:1234")
47	c.Assert(d.path, check.Equals, "path/"+defaultDiscoveryPath)
48
49	storeMock = &FakeStore{
50		Endpoints: []string{"127.0.0.1:1234", "127.0.0.2:1234", "127.0.0.3:1234"},
51	}
52	d = &Discovery{backend: store.CONSUL}
53	d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0, 0, nil)
54	d.store = storeMock
55
56	s = d.store.(*FakeStore)
57	c.Assert(s.Endpoints, check.HasLen, 3)
58	c.Assert(s.Endpoints[0], check.Equals, "127.0.0.1:1234")
59	c.Assert(s.Endpoints[1], check.Equals, "127.0.0.2:1234")
60	c.Assert(s.Endpoints[2], check.Equals, "127.0.0.3:1234")
61
62	c.Assert(d.path, check.Equals, "path/"+defaultDiscoveryPath)
63}
64
65// Extremely limited mock store so we can test initialization
66type Mock struct {
67	// Endpoints passed to InitializeMock
68	Endpoints []string
69
70	// Options passed to InitializeMock
71	Options *store.Config
72}
73
74func NewMock(endpoints []string, options *store.Config) (store.Store, error) {
75	s := &Mock{}
76	s.Endpoints = endpoints
77	s.Options = options
78	return s, nil
79}
80func (s *Mock) Put(key string, value []byte, opts *store.WriteOptions) error {
81	return errors.New("Put not supported")
82}
83func (s *Mock) Get(key string) (*store.KVPair, error) {
84	return nil, errors.New("Get not supported")
85}
86func (s *Mock) Delete(key string) error {
87	return errors.New("Delete not supported")
88}
89
90// Exists mock
91func (s *Mock) Exists(key string) (bool, error) {
92	return false, errors.New("Exists not supported")
93}
94
95// Watch mock
96func (s *Mock) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
97	return nil, errors.New("Watch not supported")
98}
99
100// WatchTree mock
101func (s *Mock) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
102	return nil, errors.New("WatchTree not supported")
103}
104
105// NewLock mock
106func (s *Mock) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
107	return nil, errors.New("NewLock not supported")
108}
109
110// List mock
111func (s *Mock) List(prefix string) ([]*store.KVPair, error) {
112	return nil, errors.New("List not supported")
113}
114
115// DeleteTree mock
116func (s *Mock) DeleteTree(prefix string) error {
117	return errors.New("DeleteTree not supported")
118}
119
120// AtomicPut mock
121func (s *Mock) AtomicPut(key string, value []byte, previous *store.KVPair, opts *store.WriteOptions) (bool, *store.KVPair, error) {
122	return false, nil, errors.New("AtomicPut not supported")
123}
124
125// AtomicDelete mock
126func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
127	return false, errors.New("AtomicDelete not supported")
128}
129
130// Close mock
131func (s *Mock) Close() {
132}
133
134func (ds *DiscoverySuite) TestInitializeWithCerts(c *check.C) {
135	cert := `-----BEGIN CERTIFICATE-----
136MIIDCDCCAfKgAwIBAgIICifG7YeiQOEwCwYJKoZIhvcNAQELMBIxEDAOBgNVBAMT
137B1Rlc3QgQ0EwHhcNMTUxMDAxMjMwMDAwWhcNMjAwOTI5MjMwMDAwWjASMRAwDgYD
138VQQDEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wRC
139O+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4+zE9h80aC4hz+6caRpds
140+J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhRSoSi3nY+B7F2E8cuz14q
141V2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZrpXUyXxAvzXfpFXo1RhSb
142UywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUerVYrCPq8vqfn//01qz55
143Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHojxOpXTBepUCIJLbtNnWFT
144V44t9gh5IqIWtoBReQIDAQABo2YwZDAOBgNVHQ8BAf8EBAMCAAYwEgYDVR0TAQH/
145BAgwBgEB/wIBAjAdBgNVHQ4EFgQUZKUI8IIjIww7X/6hvwggQK4bD24wHwYDVR0j
146BBgwFoAUZKUI8IIjIww7X/6hvwggQK4bD24wCwYJKoZIhvcNAQELA4IBAQDES2cz
1477sCQfDCxCIWH7X8kpi/JWExzUyQEJ0rBzN1m3/x8ySRxtXyGekimBqQwQdFqlwMI
148xzAQKkh3ue8tNSzRbwqMSyH14N1KrSxYS9e9szJHfUasoTpQGPmDmGIoRJuq1h6M
149ej5x1SCJ7GWCR6xEXKUIE9OftXm9TdFzWa7Ja3OHz/mXteii8VXDuZ5ACq6EE5bY
1508sP4gcICfJ5fTrpTlk9FIqEWWQrCGa5wk95PGEj+GJpNogjXQ97wVoo/Y3p1brEn
151t5zjN9PAq4H1fuCMdNNA+p1DHNwd+ELTxcMAnb2ajwHvV6lKPXutrTFc4umJToBX
152FpTxDmJHEV4bzUzh
153-----END CERTIFICATE-----
154`
155	key := `-----BEGIN RSA PRIVATE KEY-----
156MIIEpQIBAAKCAQEA1wRCO+flnLTK5ImjTurNRHwSejuqGbc4CAvpB0hS+z0QlSs4
157+zE9h80aC4hz+6caRpds+J908Q+RvAittMHbpc7VjbZP72G6fiXk7yPPl6C10HhR
158SoSi3nY+B7F2E8cuz14qV2e+ejhWhSrBb/keyXpcyjoW1BOAAJ2TIclRRkICSCZr
159pXUyXxAvzXfpFXo1RhSbUywN11pfiCQzDUN7sPww9UzFHuAHZHoyfTr27XnJYVUe
160rVYrCPq8vqfn//01qz55Xs0hvzGdlTFXhuabFtQnKFH5SNwo/fcznhB7rePOwHoj
161xOpXTBepUCIJLbtNnWFTV44t9gh5IqIWtoBReQIDAQABAoIBAHSWipORGp/uKFXj
162i/mut776x8ofsAxhnLBARQr93ID+i49W8H7EJGkOfaDjTICYC1dbpGrri61qk8sx
163qX7p3v/5NzKwOIfEpirgwVIqSNYe/ncbxnhxkx6tXtUtFKmEx40JskvSpSYAhmmO
1641XSx0E/PWaEN/nLgX/f1eWJIlxlQkk3QeqL+FGbCXI48DEtlJ9+MzMu4pAwZTpj5
1655qtXo5JJ0jRGfJVPAOznRsYqv864AhMdMIWguzk6EGnbaCWwPcfcn+h9a5LMdony
166MDHfBS7bb5tkF3+AfnVY3IBMVx7YlsD9eAyajlgiKu4zLbwTRHjXgShy+4Oussz0
167ugNGnkECgYEA/hi+McrZC8C4gg6XqK8+9joD8tnyDZDz88BQB7CZqABUSwvjDqlP
168L8hcwo/lzvjBNYGkqaFPUICGWKjeCtd8pPS2DCVXxDQX4aHF1vUur0uYNncJiV3N
169XQz4Iemsa6wnKf6M67b5vMXICw7dw0HZCdIHD1hnhdtDz0uVpeevLZ8CgYEA2KCT
170Y43lorjrbCgMqtlefkr3GJA9dey+hTzCiWEOOqn9RqGoEGUday0sKhiLofOgmN2B
171LEukpKIey8s+Q/cb6lReajDVPDsMweX8i7hz3Wa4Ugp4Xa5BpHqu8qIAE2JUZ7bU
172t88aQAYE58pUF+/Lq1QzAQdrjjzQBx6SrBxieecCgYEAvukoPZEC8mmiN1VvbTX+
173QFHmlZha3QaDxChB+QUe7bMRojEUL/fVnzkTOLuVFqSfxevaI/km9n0ac5KtAchV
174xjp2bTnBb5EUQFqjopYktWA+xO07JRJtMfSEmjZPbbay1kKC7rdTfBm961EIHaRj
175xZUf6M+rOE8964oGrdgdLlECgYEA046GQmx6fh7/82FtdZDRQp9tj3SWQUtSiQZc
176qhO59Lq8mjUXz+MgBuJXxkiwXRpzlbaFB0Bca1fUoYw8o915SrDYf/Zu2OKGQ/qa
177V81sgiVmDuEgycR7YOlbX6OsVUHrUlpwhY3hgfMe6UtkMvhBvHF/WhroBEIJm1pV
178PXZ/CbMCgYEApNWVktFBjOaYfY6SNn4iSts1jgsQbbpglg3kT7PLKjCAhI6lNsbk
179dyT7ut01PL6RaW4SeQWtrJIVQaM6vF3pprMKqlc5XihOGAmVqH7rQx9rtQB5TicL
180BFrwkQE4HQtQBV60hYQUzzlSk44VFDz+jxIEtacRHaomDRh2FtOTz+I=
181-----END RSA PRIVATE KEY-----
182`
183	certFile, err := ioutil.TempFile("", "cert")
184	c.Assert(err, check.IsNil)
185	defer os.Remove(certFile.Name())
186	certFile.Write([]byte(cert))
187	certFile.Close()
188	keyFile, err := ioutil.TempFile("", "key")
189	c.Assert(err, check.IsNil)
190	defer os.Remove(keyFile.Name())
191	keyFile.Write([]byte(key))
192	keyFile.Close()
193
194	libkv.AddStore("mock", NewMock)
195	d := &Discovery{backend: "mock"}
196	err = d.Initialize("127.0.0.3:1234", 0, 0, map[string]string{
197		"kv.cacertfile": certFile.Name(),
198		"kv.certfile":   certFile.Name(),
199		"kv.keyfile":    keyFile.Name(),
200	})
201	c.Assert(err, check.IsNil)
202	s := d.store.(*Mock)
203	c.Assert(s.Options.TLS, check.NotNil)
204	c.Assert(s.Options.TLS.RootCAs, check.NotNil)
205	c.Assert(s.Options.TLS.Certificates, check.HasLen, 1)
206}
207
208func (ds *DiscoverySuite) TestWatch(c *check.C) {
209	mockCh := make(chan []*store.KVPair)
210
211	storeMock := &FakeStore{
212		Endpoints:  []string{"127.0.0.1:1234"},
213		mockKVChan: mockCh,
214	}
215
216	d := &Discovery{backend: store.CONSUL}
217	d.Initialize("127.0.0.1:1234/path", 0, 0, nil)
218	d.store = storeMock
219
220	expected := discovery.Entries{
221		&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
222		&discovery.Entry{Host: "2.2.2.2", Port: "2222"},
223	}
224	kvs := []*store.KVPair{
225		{Key: path.Join("path", defaultDiscoveryPath, "1.1.1.1"), Value: []byte("1.1.1.1:1111")},
226		{Key: path.Join("path", defaultDiscoveryPath, "2.2.2.2"), Value: []byte("2.2.2.2:2222")},
227	}
228
229	stopCh := make(chan struct{})
230	ch, errCh := d.Watch(stopCh)
231
232	// It should fire an error since the first WatchTree call failed.
233	c.Assert(<-errCh, check.ErrorMatches, "test error")
234	// We have to drain the error channel otherwise Watch will get stuck.
235	go func() {
236		for range errCh {
237		}
238	}()
239
240	// Push the entries into the store channel and make sure discovery emits.
241	mockCh <- kvs
242	c.Assert(<-ch, check.DeepEquals, expected)
243
244	// Add a new entry.
245	expected = append(expected, &discovery.Entry{Host: "3.3.3.3", Port: "3333"})
246	kvs = append(kvs, &store.KVPair{Key: path.Join("path", defaultDiscoveryPath, "3.3.3.3"), Value: []byte("3.3.3.3:3333")})
247	mockCh <- kvs
248	c.Assert(<-ch, check.DeepEquals, expected)
249
250	close(mockCh)
251	// Give it enough time to call WatchTree.
252	time.Sleep(3 * time.Second)
253
254	// Stop and make sure it closes all channels.
255	close(stopCh)
256	c.Assert(<-ch, check.IsNil)
257	c.Assert(<-errCh, check.IsNil)
258}
259
260// FakeStore implements store.Store methods. It mocks all store
261// function in a simple, naive way.
262type FakeStore struct {
263	Endpoints  []string
264	Options    *store.Config
265	mockKVChan <-chan []*store.KVPair
266
267	watchTreeCallCount int
268}
269
270func (s *FakeStore) Put(key string, value []byte, options *store.WriteOptions) error {
271	return nil
272}
273
274func (s *FakeStore) Get(key string) (*store.KVPair, error) {
275	return nil, nil
276}
277
278func (s *FakeStore) Delete(key string) error {
279	return nil
280}
281
282func (s *FakeStore) Exists(key string) (bool, error) {
283	return true, nil
284}
285
286func (s *FakeStore) Watch(key string, stopCh <-chan struct{}) (<-chan *store.KVPair, error) {
287	return nil, nil
288}
289
290// WatchTree will fail the first time, and return the mockKVchan afterwards.
291// This is the behavior we need for testing.. If we need 'moar', should update this.
292func (s *FakeStore) WatchTree(directory string, stopCh <-chan struct{}) (<-chan []*store.KVPair, error) {
293	if s.watchTreeCallCount == 0 {
294		s.watchTreeCallCount = 1
295		return nil, errors.New("test error")
296	}
297	// First calls error
298	return s.mockKVChan, nil
299}
300
301func (s *FakeStore) NewLock(key string, options *store.LockOptions) (store.Locker, error) {
302	return nil, nil
303}
304
305func (s *FakeStore) List(directory string) ([]*store.KVPair, error) {
306	return []*store.KVPair{}, nil
307}
308
309func (s *FakeStore) DeleteTree(directory string) error {
310	return nil
311}
312
313func (s *FakeStore) AtomicPut(key string, value []byte, previous *store.KVPair, options *store.WriteOptions) (bool, *store.KVPair, error) {
314	return true, nil, nil
315}
316
317func (s *FakeStore) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
318	return true, nil
319}
320
321func (s *FakeStore) Close() {
322}
323