diff --git a/ROADMAP.md b/ROADMAP.md index 4627c6434a..b98bf16643 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -24,6 +24,6 @@ Docker Swarm Roadmap * [ ] Discovery backends * [x] etcd * [ ] zookeeper - * [ ] consul + * [x] consul * [x] hub * [x] file diff --git a/discovery/README.md b/discovery/README.md index e93490ac45..5ed6656770 100644 --- a/discovery/README.md +++ b/discovery/README.md @@ -78,6 +78,29 @@ $ swarm list --discovery etcd:/// http:// ``` +###### Using consul + +```bash +# on each of your nodes, start the swarm agent +# doesn't have to be public (eg. 192.168.0.X), +# as long as the other nodes can reach it, it is fine. +$ swarm join --discovery consul:/// --addr= + +# start the manager on any machine or your laptop +$ swarm manage --discovery consul:/// -H= + +# use the regular docker cli +$ docker -H info +$ docker -H run ... +$ docker -H ps +$ docker -H logs ... +... + +# list nodes in your cluster +$ swarm list --discovery consul:/// +http:// +``` + ## Contributing Contributing a new discovery backend is easy, diff --git a/discovery/consul/consul.go b/discovery/consul/consul.go new file mode 100644 index 0000000000..00290fa0dd --- /dev/null +++ b/discovery/consul/consul.go @@ -0,0 +1,108 @@ +package consul + +import ( + "errors" + "path" + "strings" + "time" + + log "github.com/Sirupsen/logrus" + consul "github.com/armon/consul-api" + "github.com/docker/swarm/discovery" +) + +type ConsulDiscoveryService struct { + heartbeat time.Duration + client *consul.Client + prefix string + lastIndex uint64 +} + +func init() { + discovery.Register("consul", &ConsulDiscoveryService{}) +} + +func (s *ConsulDiscoveryService) Initialize(uris string, heartbeat int) error { + parts := strings.SplitN(uris, "/", 2) + if len(parts) < 2 { + return errors.New("missing consul prefix") + } + addr := parts[0] + path := parts[1] + + config := consul.DefaultConfig() + config.Address = addr + + client, err := consul.NewClient(config) + if err != nil { + return err + } + s.client = client + s.heartbeat = time.Duration(heartbeat) * time.Second + s.prefix = path + "/" + kv := s.client.KV() + p := &consul.KVPair{Key: s.prefix, Value: nil} + if _, err = kv.Put(p, nil); err != nil { + return err + } + _, meta, err := kv.Get(s.prefix, nil) + if err != nil { + return err + } + s.lastIndex = meta.LastIndex + return nil +} +func (s *ConsulDiscoveryService) Fetch() ([]*discovery.Node, error) { + kv := s.client.KV() + pairs, _, err := kv.List(s.prefix, nil) + if err != nil { + return nil, err + } + + var nodes []*discovery.Node + + for _, pair := range pairs { + if pair.Key == s.prefix { + continue + } + nodes = append(nodes, discovery.NewNode(string(pair.Value))) + } + return nodes, nil +} + +func (s *ConsulDiscoveryService) Watch(callback discovery.WatchCallback) { + for _ = range s.waitForChange() { + nodes, err := s.Fetch() + if err == nil { + callback(nodes) + } + } +} + +func (s *ConsulDiscoveryService) Register(addr string) error { + kv := s.client.KV() + p := &consul.KVPair{Key: path.Join(s.prefix, addr), Value: []byte(addr)} + _, err := kv.Put(p, nil) + return err +} + +func (s *ConsulDiscoveryService) waitForChange() <-chan uint64 { + c := make(chan uint64) + go func() { + for { + kv := s.client.KV() + option := &consul.QueryOptions{ + WaitIndex: s.lastIndex, + WaitTime: s.heartbeat} + _, meta, err := kv.List(s.prefix, option) + if err != nil { + log.Errorln(err) + break + } + s.lastIndex = meta.LastIndex + c <- s.lastIndex + } + close(c) + }() + return c +} diff --git a/discovery/consul/consul_test.go b/discovery/consul/consul_test.go new file mode 100644 index 0000000000..79bf873bdf --- /dev/null +++ b/discovery/consul/consul_test.go @@ -0,0 +1,13 @@ +package consul + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInitialize(t *testing.T) { + discovery := &ConsulDiscoveryService{} + discovery.Initialize("127.0.0.1:8500/path", 0) + assert.Equal(t, discovery.prefix, "path/") +} diff --git a/flags.go b/flags.go index f357c3c276..a734d9a3f9 100644 --- a/flags.go +++ b/flags.go @@ -6,7 +6,7 @@ var ( flDiscovery = cli.StringFlag{ Name: "discovery", Value: "", - Usage: "DiscoveryService to use [token://, etcd://,/, file://path/to/file]", + Usage: "DiscoveryService to use [token://, etcd://,/, file://path/to/file, consul:///]", EnvVar: "SWARM_DISCOVERY", } flAddr = cli.StringFlag{ diff --git a/main.go b/main.go index cf524bdfba..4eb1d64a73 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "github.com/codegangsta/cli" "github.com/docker/swarm/discovery" + _ "github.com/docker/swarm/discovery/consul" _ "github.com/docker/swarm/discovery/etcd" _ "github.com/docker/swarm/discovery/file" "github.com/docker/swarm/discovery/token"