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