github.com/darciopacifico/docker@v1.9.0-rc1/pkg/discovery/kv/kv.go (about)

     1  package kv
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"strings"
     7  	"time"
     8  
     9  	log "github.com/Sirupsen/logrus"
    10  	"github.com/docker/docker/pkg/discovery"
    11  	"github.com/docker/docker/pkg/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  	discoveryPath = "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  	s.path = path.Join(s.prefix, discoveryPath)
    66  
    67  	var config *store.Config
    68  	if clusterOpts["kv.cacertfile"] != "" && clusterOpts["kv.certfile"] != "" && clusterOpts["kv.keyfile"] != "" {
    69  		log.Info("Initializing discovery with TLS")
    70  		tlsConfig, err := tlsconfig.Client(tlsconfig.Options{
    71  			CAFile:   clusterOpts["kv.cacertfile"],
    72  			CertFile: clusterOpts["kv.certfile"],
    73  			KeyFile:  clusterOpts["kv.keyfile"],
    74  		})
    75  		if err != nil {
    76  			return err
    77  		}
    78  		config = &store.Config{
    79  			// Set ClientTLS to trigger https (bug in libkv/etcd)
    80  			ClientTLS: &store.ClientTLSConfig{
    81  				CACertFile: clusterOpts["kv.cacertfile"],
    82  				CertFile:   clusterOpts["kv.certfile"],
    83  				KeyFile:    clusterOpts["kv.keyfile"],
    84  			},
    85  			// The actual TLS config that will be used
    86  			TLS: tlsConfig,
    87  		}
    88  	} else {
    89  		log.Info("Initializing discovery without TLS")
    90  	}
    91  
    92  	// Creates a new store, will ignore options given
    93  	// if not supported by the chosen store
    94  	s.store, err = libkv.NewStore(s.backend, addrs, config)
    95  	return err
    96  }
    97  
    98  // Watch the store until either there's a store error or we receive a stop request.
    99  // Returns false if we shouldn't attempt watching the store anymore (stop request received).
   100  func (s *Discovery) watchOnce(stopCh <-chan struct{}, watchCh <-chan []*store.KVPair, discoveryCh chan discovery.Entries, errCh chan error) bool {
   101  	for {
   102  		select {
   103  		case pairs := <-watchCh:
   104  			if pairs == nil {
   105  				return true
   106  			}
   107  
   108  			log.WithField("discovery", s.backend).Debugf("Watch triggered with %d nodes", len(pairs))
   109  
   110  			// Convert `KVPair` into `discovery.Entry`.
   111  			addrs := make([]string, len(pairs))
   112  			for _, pair := range pairs {
   113  				addrs = append(addrs, string(pair.Value))
   114  			}
   115  
   116  			entries, err := discovery.CreateEntries(addrs)
   117  			if err != nil {
   118  				errCh <- err
   119  			} else {
   120  				discoveryCh <- entries
   121  			}
   122  		case <-stopCh:
   123  			// We were requested to stop watching.
   124  			return false
   125  		}
   126  	}
   127  }
   128  
   129  // Watch is exported
   130  func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
   131  	ch := make(chan discovery.Entries)
   132  	errCh := make(chan error)
   133  
   134  	go func() {
   135  		defer close(ch)
   136  		defer close(errCh)
   137  
   138  		// Forever: Create a store watch, watch until we get an error and then try again.
   139  		// Will only stop if we receive a stopCh request.
   140  		for {
   141  			// Set up a watch.
   142  			watchCh, err := s.store.WatchTree(s.path, stopCh)
   143  			if err != nil {
   144  				errCh <- err
   145  			} else {
   146  				if !s.watchOnce(stopCh, watchCh, ch, errCh) {
   147  					return
   148  				}
   149  			}
   150  
   151  			// If we get here it means the store watch channel was closed. This
   152  			// is unexpected so let's retry later.
   153  			errCh <- fmt.Errorf("Unexpected watch error")
   154  			time.Sleep(s.heartbeat)
   155  		}
   156  	}()
   157  	return ch, errCh
   158  }
   159  
   160  // Register is exported
   161  func (s *Discovery) Register(addr string) error {
   162  	opts := &store.WriteOptions{TTL: s.ttl}
   163  	return s.store.Put(path.Join(s.path, addr), []byte(addr), opts)
   164  }
   165  
   166  // Store returns the underlying store used by KV discovery.
   167  func (s *Discovery) Store() store.Store {
   168  	return s.store
   169  }
   170  
   171  // Prefix returns the store prefix
   172  func (s *Discovery) Prefix() string {
   173  	return s.prefix
   174  }