dev privileges, db
[arvados.git] / services / boot / consul.go
index 8761b39ae4770da3ec19c34e1b220a1b5bf64143..9190aac53150a3404a34497d1e61b17362202c62 100644 (file)
@@ -5,7 +5,10 @@ import (
        "fmt"
        "os"
        "os/exec"
+       "path"
+       "strings"
        "sync"
+       "time"
 
        "github.com/hashicorp/consul/api"
 )
@@ -20,6 +23,9 @@ func (cb *consulBooter) Boot(ctx context.Context) error {
        cb.Lock()
        defer cb.Unlock()
 
+       if cb.check(ctx) == nil {
+               return nil
+       }
        cfg := cfg(ctx)
        bin := cfg.UsrDir + "/bin/consul"
        err := (&download{
@@ -31,20 +37,53 @@ func (cb *consulBooter) Boot(ctx context.Context) error {
        if err != nil {
                return err
        }
-       if cb.check(ctx) == nil {
-               return nil
-       }
-       dataDir := cfg.DataDir + "/consul"
+       dataDir := path.Join(cfg.DataDir, "consul")
        if err := os.MkdirAll(dataDir, 0700); err != nil {
                return err
        }
-       args := []string{
-               "agent",
-               "-server",
-               "-advertise=127.0.0.1",
-               "-data-dir", dataDir,
-               "-bootstrap-expect", fmt.Sprintf("%d", len(cfg.ControlHosts))}
-       supervisor := newSupervisor(ctx, "consul", bin, args...)
+       args := []string{"agent"}
+       {
+               cf := path.Join(cfg.DataDir, "consul-encrypt.json")
+               if _, err := os.Stat(cf); err != nil && !os.IsNotExist(err) {
+                       return err
+               } else if err != nil {
+                       key, err := exec.Command(bin, "keygen").CombinedOutput()
+                       if err != nil {
+                               return err
+                       }
+                       if err = atomicWriteJSON(cf, map[string]interface{}{
+                               "encrypt": strings.TrimSpace(string(key)),
+                       }, 0400); err != nil {
+                               return err
+                       }
+               }
+               args = append(args, "-config-file="+cf)
+       }
+       {
+               cf := path.Join(cfg.DataDir, "consul-ports.json")
+               err = atomicWriteJSON(cf, map[string]interface{}{
+                       "client_addr":      "0.0.0.0",
+                       "bootstrap_expect": len(cfg.ControlHosts),
+                       "data_dir":         dataDir,
+                       "datacenter":       cfg.SiteID,
+                       "server":           true,
+                       "ui":               true,
+                       "ports": map[string]int{
+                               "dns":      cfg.Ports.ConsulDNS,
+                               "http":     cfg.Ports.ConsulHTTP,
+                               "https":    cfg.Ports.ConsulHTTPS,
+                               "rpc":      cfg.Ports.ConsulRPC,
+                               "serf_lan": cfg.Ports.ConsulSerfLAN,
+                               "serf_wan": cfg.Ports.ConsulSerfWAN,
+                               "server":   cfg.Ports.ConsulServer,
+                       },
+               }, 0644)
+               if err != nil {
+                       return err
+               }
+               args = append(args, "-config-file="+cf)
+       }
+       supervisor := newSupervisor(ctx, "arvados-consul", bin, args...)
        running, err := supervisor.Running(ctx)
        if err != nil {
                return err
@@ -65,13 +104,14 @@ func (cb *consulBooter) Boot(ctx context.Context) error {
                        }
                }
        }
-       return cb.check(ctx)
+       return waitCheck(ctx, 30*time.Second, cb.check)
 }
 
 var consulCfg = api.DefaultConfig()
 
 func (cb *consulBooter) check(ctx context.Context) error {
        cfg := cfg(ctx)
+       consulCfg.Address = fmt.Sprintf("127.0.0.1:%d", cfg.Ports.ConsulHTTP)
        consulCfg.Datacenter = cfg.SiteID
        consul, err := api.NewClient(consulCfg)
        if err != nil {
@@ -83,3 +123,13 @@ func (cb *consulBooter) check(ctx context.Context) error {
        }
        return nil
 }
+
+// OnlyNode returns true if this is the only consul node.
+func (cb *consulBooter) OnlyNode() (bool, error) {
+       c, err := api.NewClient(consulCfg)
+       if err != nil {
+               return false, err
+       }
+       nodes, _, err := c.Catalog().Nodes(nil)
+       return len(nodes) == 1, err
+}