github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/pkg/kvstore/consul.go (about) 1 // Copyright 2016-2020 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kvstore 16 17 import ( 18 "bytes" 19 "context" 20 "encoding/base64" 21 "errors" 22 "fmt" 23 "io/ioutil" 24 "time" 25 26 "github.com/cilium/cilium/pkg/backoff" 27 "github.com/cilium/cilium/pkg/controller" 28 "github.com/cilium/cilium/pkg/lock" 29 "github.com/cilium/cilium/pkg/logging/logfields" 30 "github.com/cilium/cilium/pkg/option" 31 "github.com/cilium/cilium/pkg/spanstat" 32 33 consulAPI "github.com/hashicorp/consul/api" 34 "github.com/sirupsen/logrus" 35 "gopkg.in/yaml.v2" 36 ) 37 38 const ( 39 consulName = "consul" 40 41 // ConsulAddrOption is the string representing the key mapping to the value of the 42 // address for Consul. 43 ConsulAddrOption = "consul.address" 44 ConsulOptionConfig = "consul.tlsconfig" 45 46 // maxLockRetries is the number of retries attempted when acquiring a lock 47 maxLockRetries = 10 48 ) 49 50 type consulModule struct { 51 opts backendOptions 52 config *consulAPI.Config 53 } 54 55 var ( 56 //consulDummyAddress can be overwritten from test invokers using ldflags 57 consulDummyAddress = "https://127.0.0.1:8501" 58 //consulDummyConfigFile can be overwritten from test invokers using ldflags 59 consulDummyConfigFile = "/tmp/cilium-consul-certs/cilium-consul.yaml" 60 61 module = newConsulModule() 62 ) 63 64 func init() { 65 // register consul module for use 66 registerBackend(consulName, module) 67 } 68 69 func newConsulModule() backendModule { 70 return &consulModule{ 71 opts: backendOptions{ 72 ConsulAddrOption: &backendOption{ 73 description: "Addresses of consul cluster", 74 }, 75 ConsulOptionConfig: &backendOption{ 76 description: "Path to consul tls configuration file", 77 }, 78 }, 79 } 80 } 81 82 func ConsulDummyAddress() string { 83 return consulDummyAddress 84 } 85 86 func ConsulDummyConfigFile() string { 87 return consulDummyConfigFile 88 } 89 90 func (c *consulModule) createInstance() backendModule { 91 return newConsulModule() 92 } 93 94 func (c *consulModule) getName() string { 95 return consulName 96 } 97 98 func (c *consulModule) setConfigDummy() { 99 c.config = consulAPI.DefaultConfig() 100 c.config.Address = consulDummyAddress 101 yc := consulAPI.TLSConfig{} 102 b, err := ioutil.ReadFile(consulDummyConfigFile) 103 if err != nil { 104 log.WithError(err).Warnf("unable to read consul tls configuration file %s", consulDummyConfigFile) 105 } 106 107 err = yaml.Unmarshal(b, &yc) 108 if err != nil { 109 log.WithError(err).Warnf("invalid consul tls configuration in %s", consulDummyConfigFile) 110 } 111 112 c.config.TLSConfig = yc 113 } 114 115 func (c *consulModule) setConfig(opts map[string]string) error { 116 return setOpts(opts, c.opts) 117 } 118 119 func (c *consulModule) setExtraConfig(opts *ExtraOptions) error { 120 return nil 121 } 122 123 func (c *consulModule) getConfig() map[string]string { 124 return getOpts(c.opts) 125 } 126 127 func (c *consulModule) newClient(opts *ExtraOptions) (BackendOperations, chan error) { 128 errChan := make(chan error, 1) 129 backend, err := c.connectConsulClient(opts) 130 if err != nil { 131 errChan <- err 132 } 133 close(errChan) 134 return backend, errChan 135 } 136 137 func (c *consulModule) connectConsulClient(opts *ExtraOptions) (BackendOperations, error) { 138 if c.config == nil { 139 consulAddr, consulAddrSet := c.opts[ConsulAddrOption] 140 configPathOpt, configPathOptSet := c.opts[ConsulOptionConfig] 141 if !consulAddrSet { 142 return nil, fmt.Errorf("invalid consul configuration, please specify %s option", ConsulAddrOption) 143 } 144 145 if consulAddr.value == "" { 146 return nil, fmt.Errorf("invalid consul configuration, please specify %s option", ConsulAddrOption) 147 } 148 149 addr := consulAddr.value 150 c.config = consulAPI.DefaultConfig() 151 if configPathOptSet && configPathOpt.value != "" { 152 b, err := ioutil.ReadFile(configPathOpt.value) 153 if err != nil { 154 return nil, fmt.Errorf("unable to read consul tls configuration file %s: %s", configPathOpt.value, err) 155 } 156 yc := consulAPI.TLSConfig{} 157 err = yaml.Unmarshal(b, &yc) 158 if err != nil { 159 return nil, fmt.Errorf("invalid consul tls configuration in %s: %s", configPathOpt.value, err) 160 } 161 c.config.TLSConfig = yc 162 } 163 164 c.config.Address = addr 165 166 } 167 client, err := newConsulClient(c.config, opts) 168 if err != nil { 169 return nil, err 170 } 171 172 return client, nil 173 } 174 175 var ( 176 maxRetries = 30 177 ) 178 179 type consulClient struct { 180 *consulAPI.Client 181 lease string 182 controllers *controller.Manager 183 extraOptions *ExtraOptions 184 disconnectedMu lock.RWMutex 185 disconnected chan struct{} 186 } 187 188 func newConsulClient(config *consulAPI.Config, opts *ExtraOptions) (BackendOperations, error) { 189 var ( 190 c *consulAPI.Client 191 err error 192 ) 193 if config != nil { 194 c, err = consulAPI.NewClient(config) 195 } else { 196 c, err = consulAPI.NewClient(consulAPI.DefaultConfig()) 197 } 198 if err != nil { 199 return nil, err 200 } 201 202 boff := backoff.Exponential{Min: time.Duration(100) * time.Millisecond} 203 log.Info("Waiting for consul to elect a leader") 204 205 for i := 0; i < maxRetries; i++ { 206 var leader string 207 leader, err = c.Status().Leader() 208 209 if err == nil { 210 if leader != "" { 211 // happy path 212 break 213 } else { 214 err = errors.New("timeout while waiting for leader to be elected") 215 } 216 } 217 boff.Wait(context.TODO()) 218 } 219 220 if err != nil { 221 log.WithError(err).Fatal("Unable to contact consul server") 222 } 223 224 entry := &consulAPI.SessionEntry{ 225 TTL: fmt.Sprintf("%ds", int(option.Config.KVstoreLeaseTTL.Seconds())), 226 Behavior: consulAPI.SessionBehaviorDelete, 227 } 228 229 lease, _, err := c.Session().Create(entry, nil) 230 if err != nil { 231 return nil, fmt.Errorf("unable to create default lease: %s", err) 232 } 233 234 client := &consulClient{ 235 Client: c, 236 lease: lease, 237 controllers: controller.NewManager(), 238 extraOptions: opts, 239 disconnected: make(chan struct{}), 240 } 241 242 client.controllers.UpdateController(fmt.Sprintf("consul-lease-keepalive-%p", c), 243 controller.ControllerParams{ 244 DoFunc: func(ctx context.Context) error { 245 _, _, err := c.Session().Renew(lease, nil) 246 if err != nil { 247 // consider disconnected! 248 client.disconnectedMu.Lock() 249 close(client.disconnected) 250 client.disconnected = make(chan struct{}) 251 client.disconnectedMu.Unlock() 252 } 253 return err 254 }, 255 RunInterval: option.Config.KVstoreKeepAliveInterval, 256 }, 257 ) 258 259 return client, nil 260 } 261 262 type ConsulLocker struct { 263 *consulAPI.Lock 264 } 265 266 func (cl *ConsulLocker) Comparator() interface{} { 267 return nil 268 } 269 270 func (c *consulClient) LockPath(ctx context.Context, path string) (KVLocker, error) { 271 lockKey, err := c.LockOpts(&consulAPI.LockOptions{Key: getLockPath(path)}) 272 if err != nil { 273 return nil, err 274 } 275 276 for retries := 0; retries < maxLockRetries; retries++ { 277 ch, err := lockKey.Lock(nil) 278 switch { 279 case err != nil: 280 return nil, err 281 case ch == nil && err == nil: 282 Trace("Acquiring lock timed out, retrying", nil, logrus.Fields{fieldKey: path, logfields.Attempt: retries}) 283 default: 284 return &ConsulLocker{Lock: lockKey}, err 285 } 286 287 select { 288 case <-ctx.Done(): 289 return nil, fmt.Errorf("lock cancelled via context: %s", ctx.Err()) 290 default: 291 } 292 } 293 294 return nil, fmt.Errorf("maximum retries (%d) reached", maxLockRetries) 295 } 296 297 // Watch starts watching for changes in a prefix 298 func (c *consulClient) Watch(w *Watcher) { 299 // Last known state of all KVPairs matching the prefix 300 localState := map[string]consulAPI.KVPair{} 301 nextIndex := uint64(0) 302 303 qo := consulAPI.QueryOptions{ 304 WaitTime: time.Second, 305 } 306 307 for { 308 // Initialize sleep time to a millisecond as we don't 309 // want to sleep in between successful watch cycles 310 sleepTime := 1 * time.Millisecond 311 312 qo.WaitIndex = nextIndex 313 pairs, q, err := c.KV().List(w.prefix, &qo) 314 if err != nil { 315 sleepTime = 5 * time.Second 316 Trace("List of Watch failed", err, logrus.Fields{fieldPrefix: w.prefix, fieldWatcher: w.name}) 317 } 318 319 if q != nil { 320 nextIndex = q.LastIndex 321 } 322 323 // timeout while watching for changes, re-schedule 324 if qo.WaitIndex != 0 && (q == nil || q.LastIndex == qo.WaitIndex) { 325 goto wait 326 } 327 328 for _, newPair := range pairs { 329 oldPair, ok := localState[newPair.Key] 330 331 // Keys reported for the first time must be new 332 if !ok { 333 if newPair.CreateIndex != newPair.ModifyIndex { 334 log.Debugf("consul: Previously unknown key %s received with CreateIndex(%d) != ModifyIndex(%d)", 335 newPair.Key, newPair.CreateIndex, newPair.ModifyIndex) 336 } 337 338 queueStart := spanstat.Start() 339 w.Events <- KeyValueEvent{ 340 Typ: EventTypeCreate, 341 Key: newPair.Key, 342 Value: newPair.Value, 343 } 344 trackEventQueued(newPair.Key, EventTypeCreate, queueStart.End(true).Total()) 345 } else if oldPair.ModifyIndex != newPair.ModifyIndex { 346 queueStart := spanstat.Start() 347 w.Events <- KeyValueEvent{ 348 Typ: EventTypeModify, 349 Key: newPair.Key, 350 Value: newPair.Value, 351 } 352 trackEventQueued(newPair.Key, EventTypeModify, queueStart.End(true).Total()) 353 } 354 355 // Everything left on localState will be assumed to 356 // have been deleted, therefore remove all keys in 357 // localState that still exist in the kvstore 358 delete(localState, newPair.Key) 359 } 360 361 for k, deletedPair := range localState { 362 queueStart := spanstat.Start() 363 w.Events <- KeyValueEvent{ 364 Typ: EventTypeDelete, 365 Key: deletedPair.Key, 366 Value: deletedPair.Value, 367 } 368 trackEventQueued(deletedPair.Key, EventTypeDelete, queueStart.End(true).Total()) 369 delete(localState, k) 370 } 371 372 for _, newPair := range pairs { 373 localState[newPair.Key] = *newPair 374 375 } 376 377 // Initial list operation has been completed, signal this 378 if qo.WaitIndex == 0 { 379 w.Events <- KeyValueEvent{Typ: EventTypeListDone} 380 } 381 382 wait: 383 select { 384 case <-time.After(sleepTime): 385 case <-w.stopWatch: 386 close(w.Events) 387 w.stopWait.Done() 388 return 389 } 390 } 391 } 392 393 func (c *consulClient) waitForInitLock() <-chan struct{} { 394 initLockSucceeded := make(chan struct{}) 395 396 go func() { 397 for { 398 locker, err := c.LockPath(context.TODO(), InitLockPath) 399 if err == nil { 400 close(initLockSucceeded) 401 locker.Unlock() 402 log.Info("Distributed lock successful, consul has quorum") 403 return 404 } 405 406 time.Sleep(100 * time.Millisecond) 407 } 408 }() 409 410 return initLockSucceeded 411 } 412 413 // Connected closes the returned channel when the etcd client is connected. 414 func (c *consulClient) Connected() <-chan struct{} { 415 ch := make(chan struct{}) 416 go func() { 417 for { 418 // TODO find out if there's a better way to do this for consul 419 _, _, err := c.Session().Info(c.lease, &consulAPI.QueryOptions{}) 420 if err == nil { 421 break 422 } 423 time.Sleep(100 * time.Millisecond) 424 } 425 <-c.waitForInitLock() 426 close(ch) 427 }() 428 return ch 429 } 430 431 // Disconnected closes the returned channel when consul detects the client 432 // is disconnected from the server. 433 func (c *consulClient) Disconnected() <-chan struct{} { 434 c.disconnectedMu.RLock() 435 ch := c.disconnected 436 c.disconnectedMu.RUnlock() 437 return ch 438 } 439 440 func (c *consulClient) Status() (string, error) { 441 leader, err := c.Client.Status().Leader() 442 return "Consul: " + leader, err 443 } 444 445 func (c *consulClient) DeletePrefix(path string) (err error) { 446 defer func() { Trace("DeletePrefix", err, logrus.Fields{fieldPrefix: path}) }() 447 duration := spanstat.Start() 448 _, err = c.Client.KV().DeleteTree(path, nil) 449 increaseMetric(path, metricDelete, "DeletePrefix", duration.EndError(err).Total(), err) 450 return err 451 } 452 453 // Set sets value of key 454 func (c *consulClient) Set(key string, value []byte) (err error) { 455 defer func() { Trace("Set", err, logrus.Fields{fieldKey: key, fieldValue: string(value)}) }() 456 duration := spanstat.Start() 457 _, err = c.KV().Put(&consulAPI.KVPair{Key: key, Value: value}, nil) 458 increaseMetric(key, metricSet, "Set", duration.EndError(err).Total(), err) 459 return err 460 } 461 462 // DeleteIfLocked deletes a key if the client is still holding the given lock. 463 func (c *consulClient) DeleteIfLocked(key string, lock KVLocker) (err error) { 464 defer func() { Trace("DeleteIfLocked", err, logrus.Fields{fieldKey: key}) }() 465 err = c.Delete(key) 466 return err 467 } 468 469 // Delete deletes a key 470 func (c *consulClient) Delete(key string) (err error) { 471 defer func() { Trace("Delete", err, logrus.Fields{fieldKey: key}) }() 472 duration := spanstat.Start() 473 _, err = c.KV().Delete(key, nil) 474 increaseMetric(key, metricDelete, "Delete", duration.EndError(err).Total(), err) 475 return err 476 } 477 478 // GetIfLocked returns value of key if the client is still holding the given lock. 479 func (c *consulClient) GetIfLocked(key string, lock KVLocker) (bv []byte, err error) { 480 defer func() { Trace("GetIfLocked", err, logrus.Fields{fieldKey: key, fieldValue: string(bv)}) }() 481 bv, err = c.Get(key) 482 return bv, err 483 } 484 485 // Get returns value of key 486 func (c *consulClient) Get(key string) (bv []byte, err error) { 487 defer func() { Trace("Get", err, logrus.Fields{fieldKey: key, fieldValue: string(bv)}) }() 488 duration := spanstat.Start() 489 var pair *consulAPI.KVPair 490 pair, _, err = c.KV().Get(key, nil) 491 increaseMetric(key, metricRead, "Get", duration.EndError(err).Total(), err) 492 if err != nil { 493 return nil, err 494 } 495 if pair == nil { 496 return nil, nil 497 } 498 return pair.Value, nil 499 } 500 501 // GetPrefixIfLocked returns the first key which matches the prefix and its value if the client is still holding the given lock. 502 func (c *consulClient) GetPrefixIfLocked(ctx context.Context, prefix string, lock KVLocker) (k string, bv []byte, err error) { 503 defer func() { 504 Trace("GetPrefixIfLocked", err, logrus.Fields{fieldPrefix: prefix, fieldKey: k, fieldValue: string(bv)}) 505 }() 506 k, bv, err = c.getPrefix(ctx, prefix) 507 return k, bv, err 508 } 509 510 // GetPrefix returns the first key which matches the prefix and its value 511 func (c *consulClient) GetPrefix(ctx context.Context, prefix string) (k string, bv []byte, err error) { 512 defer func() { 513 Trace("GetPrefix", err, logrus.Fields{fieldPrefix: prefix, fieldKey: k, fieldValue: string(bv)}) 514 }() 515 k, bv, err = c.getPrefix(ctx, prefix) 516 return k, bv, err 517 } 518 519 func (c *consulClient) getPrefix(ctx context.Context, prefix string) (k string, bv []byte, err error) { 520 duration := spanstat.Start() 521 opts := &consulAPI.QueryOptions{} 522 pairs, _, err := c.KV().List(prefix, opts.WithContext(ctx)) 523 increaseMetric(prefix, metricRead, "GetPrefix", duration.EndError(err).Total(), err) 524 if err != nil { 525 return "", nil, err 526 } 527 528 if len(pairs) == 0 { 529 return "", nil, nil 530 } 531 532 return pairs[0].Key, pairs[0].Value, nil 533 } 534 535 // UpdateIfLocked atomically creates a key or fails if it already exists if the client is still holding the given lock. 536 func (c *consulClient) UpdateIfLocked(ctx context.Context, key string, value []byte, lease bool, lock KVLocker) error { 537 return c.Update(ctx, key, value, lease) 538 } 539 540 // Update creates or updates a key with the value 541 func (c *consulClient) Update(ctx context.Context, key string, value []byte, lease bool) (err error) { 542 defer func() { 543 Trace("Update", err, logrus.Fields{fieldKey: key, fieldValue: string(value), fieldAttachLease: lease}) 544 }() 545 546 k := &consulAPI.KVPair{Key: key, Value: value} 547 548 if lease { 549 k.Session = c.lease 550 } 551 552 opts := &consulAPI.WriteOptions{} 553 554 duration := spanstat.Start() 555 _, err = c.KV().Put(k, opts.WithContext(ctx)) 556 increaseMetric(key, metricSet, "Update", duration.EndError(err).Total(), err) 557 return err 558 } 559 560 // UpdateIfDifferentIfLocked updates a key if the value is different and if the client is still holding the given lock. 561 func (c *consulClient) UpdateIfDifferentIfLocked(ctx context.Context, key string, value []byte, lease bool, lock KVLocker) (recreated bool, err error) { 562 defer func() { 563 Trace("UpdateIfDifferentIfLocked", err, logrus.Fields{fieldKey: key, fieldValue: value, fieldAttachLease: lease, "recreated": recreated}) 564 }() 565 566 return c.updateIfDifferent(ctx, key, value, lease) 567 } 568 569 // UpdateIfDifferent updates a key if the value is different 570 func (c *consulClient) UpdateIfDifferent(ctx context.Context, key string, value []byte, lease bool) (recreated bool, err error) { 571 defer func() { 572 Trace("UpdateIfDifferent", err, logrus.Fields{fieldKey: key, fieldValue: value, fieldAttachLease: lease, "recreated": recreated}) 573 }() 574 575 return c.updateIfDifferent(ctx, key, value, lease) 576 } 577 578 func (c *consulClient) updateIfDifferent(ctx context.Context, key string, value []byte, lease bool) (bool, error) { 579 duration := spanstat.Start() 580 getR, _, err := c.KV().Get(key, nil) 581 increaseMetric(key, metricRead, "Get", duration.EndError(err).Total(), err) 582 // On error, attempt update blindly 583 if err != nil || getR == nil { 584 return true, c.Update(ctx, key, value, lease) 585 } 586 587 if lease && getR.Session != c.lease { 588 return true, c.Update(ctx, key, value, lease) 589 } 590 591 // if lease is different and value is not equal then update. 592 if !bytes.Equal(getR.Value, value) { 593 return true, c.Update(ctx, key, value, lease) 594 } 595 596 return false, nil 597 } 598 599 // CreateOnlyIfLocked atomically creates a key if the client is still holding the given lock or fails if it already exists 600 func (c *consulClient) CreateOnlyIfLocked(ctx context.Context, key string, value []byte, lease bool, lock KVLocker) (success bool, err error) { 601 defer func() { 602 Trace("CreateOnlyIfLocked", err, logrus.Fields{fieldKey: key, fieldValue: value, fieldAttachLease: lease, "success": success}) 603 }() 604 return c.createOnly(ctx, key, value, lease) 605 } 606 607 // CreateOnly creates a key with the value and will fail if the key already exists 608 func (c *consulClient) CreateOnly(ctx context.Context, key string, value []byte, lease bool) (success bool, err error) { 609 defer func() { 610 Trace("CreateOnly", err, logrus.Fields{fieldKey: key, fieldValue: value, fieldAttachLease: lease, "success": success}) 611 }() 612 613 return c.createOnly(ctx, key, value, lease) 614 } 615 616 func (c *consulClient) createOnly(ctx context.Context, key string, value []byte, lease bool) (bool, error) { 617 k := &consulAPI.KVPair{ 618 Key: key, 619 Value: value, 620 CreateIndex: 0, 621 } 622 623 if lease { 624 k.Session = c.lease 625 } 626 opts := &consulAPI.WriteOptions{} 627 628 duration := spanstat.Start() 629 success, _, err := c.KV().CAS(k, opts.WithContext(ctx)) 630 increaseMetric(key, metricSet, "CreateOnly", duration.EndError(err).Total(), err) 631 if err != nil { 632 return false, fmt.Errorf("unable to compare-and-swap: %s", err) 633 } 634 return success, nil 635 } 636 637 // createIfExists creates a key with the value only if key condKey exists 638 func (c *consulClient) createIfExists(condKey, key string, value []byte, lease bool) error { 639 // Consul does not support transactions which would allow to check for 640 // the presence of a conditional key if the key is not the key being 641 // manipulated 642 // 643 // Lock the conditional key to serialize all CreateIfExists() calls 644 645 l, err := LockPath(context.Background(), c, condKey) 646 if err != nil { 647 return fmt.Errorf("unable to lock condKey for CreateIfExists: %s", err) 648 } 649 650 defer l.Unlock() 651 652 // Create the key if it does not exist 653 if _, err := c.CreateOnly(context.TODO(), key, value, lease); err != nil { 654 return err 655 } 656 657 // Consul does not support transactions which would allow to check for 658 // the presence of another key 659 masterKey, err := c.Get(condKey) 660 if err != nil || masterKey == nil { 661 c.Delete(key) 662 return fmt.Errorf("conditional key not present") 663 } 664 665 return nil 666 } 667 668 // CreateIfExists creates a key with the value only if key condKey exists 669 func (c *consulClient) CreateIfExists(condKey, key string, value []byte, lease bool) (err error) { 670 defer func() { 671 Trace("CreateIfExists", err, logrus.Fields{fieldKey: key, fieldValue: string(value), fieldCondition: condKey, fieldAttachLease: lease}) 672 }() 673 duration := spanstat.Start() 674 err = c.createIfExists(condKey, key, value, lease) 675 increaseMetric(key, metricSet, "CreateIfExists", duration.EndError(err).Total(), err) 676 return err 677 } 678 679 // ListPrefixIfLocked returns a list of keys matching the prefix only if the client is still holding the given lock. 680 func (c *consulClient) ListPrefixIfLocked(prefix string, lock KVLocker) (v KeyValuePairs, err error) { 681 defer func() { Trace("ListPrefixIfLocked", err, logrus.Fields{fieldPrefix: prefix, fieldNumEntries: len(v)}) }() 682 v, err = c.ListPrefix(prefix) 683 return v, err 684 } 685 686 // ListPrefix returns a map of matching keys 687 func (c *consulClient) ListPrefix(prefix string) (v KeyValuePairs, err error) { 688 defer func() { Trace("ListPrefix", err, logrus.Fields{fieldPrefix: prefix, fieldNumEntries: len(v)}) }() 689 duration := spanstat.Start() 690 691 var pairs consulAPI.KVPairs 692 pairs, _, err = c.KV().List(prefix, nil) 693 increaseMetric(prefix, metricRead, "ListPrefix", duration.EndError(err).Total(), err) 694 if err != nil { 695 return nil, err 696 } 697 698 p := KeyValuePairs(make(map[string]Value, len(pairs))) 699 for i := 0; i < len(pairs); i++ { 700 p[pairs[i].Key] = Value{ 701 Data: pairs[i].Value, 702 ModRevision: pairs[i].ModifyIndex, 703 SessionID: pairs[i].Session, 704 } 705 } 706 707 return p, nil 708 } 709 710 // Close closes the consul session 711 func (c *consulClient) Close() { 712 if c.controllers != nil { 713 c.controllers.RemoveAll() 714 } 715 if c.lease != "" { 716 c.Session().Destroy(c.lease, nil) 717 } 718 } 719 720 // GetCapabilities returns the capabilities of the backend 721 func (c *consulClient) GetCapabilities() Capabilities { 722 return Capabilities(0) 723 } 724 725 // Encode encodes a binary slice into a character set that the backend supports 726 func (c *consulClient) Encode(in []byte) (out string) { 727 defer func() { Trace("Encode", nil, logrus.Fields{"in": in, "out": out}) }() 728 return base64.URLEncoding.EncodeToString([]byte(in)) 729 } 730 731 // Decode decodes a key previously encoded back into the original binary slice 732 func (c *consulClient) Decode(in string) (out []byte, err error) { 733 defer func() { Trace("Decode", err, logrus.Fields{"in": in, "out": out}) }() 734 return base64.URLEncoding.DecodeString(in) 735 } 736 737 // ListAndWatch implements the BackendOperations.ListAndWatch using consul 738 func (c *consulClient) ListAndWatch(name, prefix string, chanSize int) *Watcher { 739 w := newWatcher(name, prefix, chanSize) 740 741 log.WithField(fieldWatcher, w).Debug("Starting watcher...") 742 743 go c.Watch(w) 744 745 return w 746 }