github.com/resin-io/docker@v1.13.1/pkg/discovery/kv/kv.go (about)

     1  package kv
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/Sirupsen/logrus"
    10  	"github.com/docker/docker/pkg/discovery"
    11  	"github.com/docker/go-connections/tlsconfig"
    12  	"github.com/docker/libkv"
    13  	"github.com/docker/libkv/store"
    14  	"github.com/docker/libkv/store/consul"
    15  	"github.com/docker/libkv/store/etcd"
    16  	"github.com/docker/libkv/store/zookeeper"
    17  )
    18  
    19  const (
    20  	defaultDiscoveryPath = "docker/nodes"
    21  )
    22  
    23  // Discovery is exported
    24  type Discovery struct {
    25  	backend   store.Backend
    26  	store     store.Store
    27  	heartbeat time.Duration
    28  	ttl       time.Duration
    29  	prefix    string
    30  	path      string
    31  }
    32  
    33  func init() {
    34  	Init()
    35  }
    36  
    37  // Init is exported
    38  func Init() {
    39  	// Register to libkv
    40  	zookeeper.Register()
    41  	consul.Register()
    42  	etcd.Register()
    43  
    44  	// Register to internal discovery service
    45  	discovery.Register("zk", &Discovery{backend: store.ZK})
    46  	discovery.Register("consul", &Discovery{backend: store.CONSUL})
    47  	discovery.Register("etcd", &Discovery{backend: store.ETCD})
    48  }
    49  
    50  // Initialize is exported
    51  func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) error {
    52  	var (
    53  		parts = strings.SplitN(uris, "/", 2)
    54  		addrs = strings.Split(parts[0], ",")
    55  		err   error
    56  	)
    57  
    58  	// A custom prefix to the path can be optionally used.
    59  	if len(parts) == 2 {
    60  		s.prefix = parts[1]
    61  	}
    62  
    63  	s.heartbeat = heartbeat
    64  	s.ttl = ttl
    65  
    66  	// Use a custom path if specified in discovery options
    67  	dpath := defaultDiscoveryPath
    68  	if clusterOpts["kv.path"] != "" {
    69  		dpath = clusterOpts["kv.path"]
    70  	}
    71  
    72  	s.path = path.Join(s.prefix, dpath)
    73  
    74  	var config *store.Config
    75  	if clusterOpts["kv.cacertfile"] != "" && clusterOpts["kv.certfile"] != "" && clusterOpts["kv.keyfile"] != "" {
    76  		logrus.Info("Initializing discovery with TLS")
    77  		tlsConfig, err := tlsconfig.Client(tlsconfig.Options{
    78  			CAFile:   clusterOpts["kv.cacertfile"],
    79  			CertFile: clusterOpts["kv.certfile"],
    80  			KeyFile:  clusterOpts["kv.keyfile"],
    81  		})
    82  		if err != nil {
    83  			return err
    84  		}
    85  		config = &store.Config{
    86  			// Set ClientTLS to trigger https (bug in libkv/etcd)
    87  			ClientTLS: &store.ClientTLSConfig{
    88  				CACertFile: clusterOpts["kv.cacertfile"],
    89  				CertFile:   clusterOpts["kv.certfile"],
    90  				KeyFile:    clusterOpts["kv.keyfile"],
    91  			},
    92  			// The actual TLS config that will be used
    93  			TLS: tlsConfig,
    94  		}
    95  	} else {
    96  		logrus.Info("Initializing discovery without TLS")
    97  	}
    98  
    99  	// Creates a new store, will ignore options given
   100  	// if not supported by the chosen store
   101  	s.store, err = libkv.NewStore(s.backend, addrs, config)
   102  	return err
   103  }
   104  
   105  // Watch the store until either there's a store error or we receive a stop request.
   106  // Returns false if we shouldn't attempt watching the store anymore (stop request received).
   107  func (s *Discovery) watchOnce(stopCh <-chan struct{}, watchCh <-chan []*store.KVPair, discoveryCh chan discovery.Entries, errCh chan error) bool {
   108  	for {
   109  		select {
   110  		case pairs := <-watchCh:
   111  			if pairs == nil {
   112  				return true
   113  			}
   114  
   115  			logrus.WithField("discovery", s.backend).Debugf("Watch triggered with %d nodes", len(pairs))
   116  
   117  			// Convert `KVPair` into `discovery.Entry`.
   118  			addrs := make([]string, len(pairs))
   119  			for _, pair := range pairs {
   120  				addrs = append(addrs, string(pair.Value))
   121  			}
   122  
   123  			entries, err := discovery.CreateEntries(addrs)
   124  			if err != nil {
   125  				errCh <- err
   126  			} else {
   127  				discoveryCh <- entries
   128  			}
   129  		case <-stopCh:
   130  			// We were requested to stop watching.
   131  			return false
   132  		}
   133  	}
   134  }
   135  
   136  // Watch is exported
   137  func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
   138  	ch := make(chan discovery.Entries)
   139  	errCh := make(chan error)
   140  
   141  	go func() {
   142  		defer close(ch)
   143  		defer close(errCh)
   144  
   145  		// Forever: Create a store watch, watch until we get an error and then try again.
   146  		// Will only stop if we receive a stopCh request.
   147  		for {
   148  			// Create the path to watch if it does not exist yet
   149  			exists, err := s.store.Exists(s.path)
   150  			if err != nil {
   151  				errCh <- err
   152  			}
   153  			if !exists {
   154  				if err := s.store.Put(s.path, []byte(""), &store.WriteOptions{IsDir: true}); err != nil {
   155  					errCh <- err
   156  				}
   157  			}
   158  
   159  			// Set up a watch.
   160  			watchCh, err := s.store.WatchTree(s.path, stopCh)
   161  			if err != nil {
   162  				errCh <- err
   163  			} else {
   164  				if !s.watchOnce(stopCh, watchCh, ch, errCh) {
   165  					return
   166  				}
   167  			}
   168  
   169  			// If we get here it means the store watch channel was closed. This
   170  			// is unexpected so let's retry later.
   171  			errCh <- fmt.Errorf("Unexpected watch error")
   172  			time.Sleep(s.heartbeat)
   173  		}
   174  	}()
   175  	return ch, errCh
   176  }
   177  
   178  // Register is exported
   179  func (s *Discovery) Register(addr string) error {
   180  	opts := &store.WriteOptions{TTL: s.ttl}
   181  	return s.store.Put(path.Join(s.path, addr), []byte(addr), opts)
   182  }
   183  
   184  // Store returns the underlying store used by KV discovery.
   185  func (s *Discovery) Store() store.Store {
   186  	return s.store
   187  }
   188  
   189  // Prefix returns the store prefix
   190  func (s *Discovery) Prefix() string {
   191  	return s.prefix
   192  }