func (h *hostDiscovery) StartDiscovery(cfg *config.ClusterCfg, joinCallback JoinCallback, leaveCallback LeaveCallback) error { if cfg == nil { return fmt.Errorf("discovery requires a valid configuration") } hb := cfg.Heartbeat if hb == 0 { hb = defaultHeartbeat } d, err := discovery.New(cfg.Discovery, hb) if err != nil { return err } if ip := net.ParseIP(cfg.Address); ip == nil { return errors.New("Address config should be either ipv4 or ipv6 address") } if err := d.Register(cfg.Address + ":0"); err != nil { return err } h.Lock() h.discovery = d h.Unlock() go d.Watch(func(entries []*discovery.Entry) { h.processCallback(entries, joinCallback, leaveCallback) }) go sustainHeartbeat(d, hb, cfg, h.stopChan) return nil }
func (h *hostDiscovery) StartDiscovery(cfg *config.ClusterCfg, joinCallback JoinCallback, leaveCallback LeaveCallback) error { if cfg == nil { return fmt.Errorf("discovery requires a valid configuration") } hb := time.Duration(cfg.Heartbeat) * time.Second if hb == 0 { hb = defaultHeartbeat } d, err := discovery.New(cfg.Discovery, hb, TTLFactor*hb) if err != nil { return err } if ip := net.ParseIP(cfg.Address); ip == nil { return errors.New("address config should be either ipv4 or ipv6 address") } if err := d.Register(cfg.Address + ":0"); err != nil { return err } h.Lock() h.discovery = d h.Unlock() discoveryCh, errCh := d.Watch(h.stopChan) go h.monitorDiscovery(discoveryCh, errCh, joinCallback, leaveCallback) go h.sustainHeartbeat(d, hb, cfg) return nil }
func NewCluster(scheduler *scheduler.Scheduler, store *state.Store, eventhandler cluster.EventHandler, options *cluster.Options) cluster.Cluster { log.WithFields(log.Fields{"name": "swarm"}).Debug("Initializing cluster") cluster := &Cluster{ eventHandler: eventhandler, nodes: make(map[string]*node), scheduler: scheduler, options: options, store: store, } // get the list of entries from the discovery service go func() { d, err := discovery.New(options.Discovery, options.Heartbeat) if err != nil { log.Fatal(err) } entries, err := d.Fetch() if err != nil { log.Fatal(err) } cluster.newEntries(entries) go d.Watch(cluster.newEntries) }() return cluster }
func list(c *cli.Context) { dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to list a cluster. See '%s list --help'.", c.App.Name) } timeout, err := time.ParseDuration(c.String("timeout")) if err != nil { log.Fatalf("invalid --timeout: %v", err) } d, err := discovery.New(dflag, timeout, 0) if err != nil { log.Fatal(err) } ch, errCh := d.Watch(nil) select { case entries := <-ch: for _, entry := range entries { fmt.Println(entry) } case err := <-errCh: log.Fatal(err) case <-time.After(timeout): log.Fatal("Timed out") } }
// Initialize the discovery service. func createDiscovery(uri string, c *cli.Context, clusterStoreOpt []string) discovery.Discovery { hb, err := time.ParseDuration(c.String("heartbeat")) if err != nil { log.Fatalf("invalid --heartbeat: %v", err) } if hb < 1*time.Second { log.Fatal("--heartbeat should be at least one second") } // Process the store options options := map[string]string{} for _, option := range clusterStoreOpt { if !strings.Contains(option, "=") { log.Fatal("--cluster-store-opt must container key=value strings") } kvpair := strings.SplitN(option, "=", 2) options[kvpair[0]] = kvpair[1] } // Set up discovery. discovery, err := discovery.New(uri, hb, 0, options) if err != nil { log.Fatal(err) } return discovery }
func join(c *cli.Context) { dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to join a cluster. See '%s join --help'.", c.App.Name) } d, err := discovery.New(dflag, c.Int("heartbeat")) if err != nil { log.Fatal(err) } addr := c.String("addr") if !checkAddrFormat(addr) { log.Fatal("--addr should be of the form ip:port or hostname:port") } if err := d.Register(addr); err != nil { log.Fatal(err) } hb := time.Duration(c.Int("heartbeat")) for { log.WithFields(log.Fields{"addr": addr, "discovery": dflag}).Infof("Registering on the discovery service every %d seconds...", hb) time.Sleep(hb * time.Second) if err := d.Register(addr); err != nil { log.Error(err) } } }
func join(c *cli.Context) { dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to join a cluster. See '%s join --help'.", c.App.Name) } addr := c.String("advertise") if addr == "" { log.Fatal("missing mandatory --advertise flag") } if !checkAddrFormat(addr) { log.Fatal("--advertise should be of the form ip:port or hostname:port") } joinDelay, err := time.ParseDuration(c.String("delay")) if err != nil { log.Fatalf("invalid --delay: %v", err) } hb, err := time.ParseDuration(c.String("heartbeat")) if err != nil { log.Fatalf("invalid --heartbeat: %v", err) } if hb < 1*time.Second { log.Fatal("--heartbeat should be at least one second") } ttl, err := time.ParseDuration(c.String("ttl")) if err != nil { log.Fatalf("invalid --ttl: %v", err) } if ttl <= hb { log.Fatal("--ttl must be strictly superior to the heartbeat value") } d, err := discovery.New(dflag, hb, ttl, getDiscoveryOpt(c)) if err != nil { log.Fatal(err) } // add a random delay between 0s and joinDelay at start to avoid synchronized registration if joinDelay > 0 { r := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) delay := time.Duration(r.Int63n(int64(joinDelay))) log.Infof("Add a random delay %s to avoid synchronized registration", delay) time.Sleep(delay) } for { log.WithFields(log.Fields{"addr": addr, "discovery": dflag}).Infof("Registering on the discovery service every %s...", hb) if err := d.Register(addr); err != nil { log.Error(err) } time.Sleep(hb) } }
func join(c *cli.Context) { dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to join a cluster. See '%s join --help'.", c.App.Name) } addr := c.String("advertise") if addr == "" { log.Fatal("missing mandatory --advertise flag") } if !checkAddrFormat(addr) { log.Fatal("--advertise should be of the form ip:port or hostname:port") } hb, err := time.ParseDuration(c.String("heartbeat")) if err != nil { log.Fatalf("invalid --heartbeat: %v", err) } if hb < 1*time.Second { log.Fatal("--heartbeat should be at least one second") } ttl, err := time.ParseDuration(c.String("ttl")) if err != nil { log.Fatalf("invalid --ttl: %v", err) } if ttl <= hb { log.Fatal("--ttl must be strictly superior to the heartbeat value") } // Process the store options options := map[string]string{} for _, option := range c.StringSlice("cluster-store-opt") { if !strings.Contains(option, "=") { log.Fatal("--cluster-store-opt must container key=value strings") } kvpair := strings.SplitN(option, "=", 2) options[kvpair[0]] = kvpair[1] } d, err := discovery.New(dflag, hb, ttl, options) if err != nil { log.Fatal(err) } for { log.WithFields(log.Fields{"addr": addr, "discovery": dflag}).Infof("Registering on the discovery service every %s...", hb) if err := d.Register(addr); err != nil { log.Error(err) } time.Sleep(hb) } }
// Initialize the discovery service. func createDiscovery(uri string, c *cli.Context, discoveryOpt []string) discovery.Discovery { hb, err := time.ParseDuration(c.String("heartbeat")) if err != nil { log.Fatalf("invalid --heartbeat: %v", err) } if hb < 1*time.Second { log.Fatal("--heartbeat should be at least one second") } // Set up discovery. discovery, err := discovery.New(uri, hb, 0, getDiscoveryOpt(c)) if err != nil { log.Fatal(err) } return discovery }
func join(c *cli.Context) { dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to join a cluster. See '%s join --help'.", c.App.Name) } addr := c.String("advertise") if addr == "" { log.Fatal("missing mandatory --advertise flag") } if !checkAddrFormat(addr) { log.Fatal("--advertise should be of the form ip:port or hostname:port") } hb, err := time.ParseDuration(c.String("heartbeat")) if err != nil { log.Fatalf("invalid --heartbeat: %v", err) } if hb < 1*time.Second { log.Fatal("--heartbeat should be at least one second") } ttl, err := time.ParseDuration(c.String("ttl")) if err != nil { log.Fatalf("invalid --ttl: %v", err) } if ttl <= hb { log.Fatal("--ttl must be strictly superior to the heartbeat value") } d, err := discovery.New(dflag, hb, ttl, getDiscoveryOpt(c)) if err != nil { log.Fatal(err) } for { log.WithFields(log.Fields{"addr": addr, "discovery": dflag}).Infof("Registering on the discovery service every %s...", hb) if err := d.Register(addr); err != nil { log.Error(err) } time.Sleep(hb) } }
func list(c *cli.Context) { dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to list a cluster. See '%s list --help'.", c.App.Name) } timeout, err := time.ParseDuration(c.String("timeout")) if err != nil { log.Fatalf("invalid --timeout: %v", err) } // Process the store options options := map[string]string{} for _, option := range c.StringSlice("cluster-store-opt") { if !strings.Contains(option, "=") { log.Fatal("--cluster-store-opt must container key=value strings") } kvpair := strings.SplitN(option, "=", 2) options[kvpair[0]] = kvpair[1] } d, err := discovery.New(dflag, timeout, 0, options) if err != nil { log.Fatal(err) } ch, errCh := d.Watch(nil) select { case entries := <-ch: for _, entry := range entries { fmt.Println(entry) } case err := <-errCh: log.Fatal(err) case <-time.After(timeout): log.Fatal("Timed out") } }
func main() { app := cli.NewApp() app.Name = path.Base(os.Args[0]) app.Usage = "a Docker-native clustering system" app.Version = version.VERSION + " (" + version.GITCOMMIT + ")" app.Author = "" app.Email = "" app.Flags = []cli.Flag{ cli.BoolFlag{ Name: "debug", Usage: "debug mode", EnvVar: "DEBUG", }, cli.StringFlag{ Name: "log-level, l", Value: "info", Usage: fmt.Sprintf("Log level (options: debug, info, warn, error, fatal, panic)"), }, } // logs app.Before = func(c *cli.Context) error { log.SetOutput(os.Stderr) level, err := log.ParseLevel(c.String("log-level")) if err != nil { log.Fatalf(err.Error()) } log.SetLevel(level) // If a log level wasn't specified and we are running in debug mode, // enforce log-level=debug. if !c.IsSet("log-level") && !c.IsSet("l") && c.Bool("debug") { log.SetLevel(log.DebugLevel) } return nil } app.Commands = []cli.Command{ { Name: "create", ShortName: "c", Usage: "create a cluster", Action: func(c *cli.Context) { discovery := &token.TokenDiscoveryService{} discovery.Initialize("", 0) token, err := discovery.CreateCluster() if err != nil { log.Fatal(err) } fmt.Println(token) }, }, { Name: "list", ShortName: "l", Usage: "list nodes in a cluster", Action: func(c *cli.Context) { dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to list a cluster. See '%s list --help'.", c.App.Name) } d, err := discovery.New(dflag, 0) if err != nil { log.Fatal(err) } nodes, err := d.Fetch() if err != nil { log.Fatal(err) } for _, node := range nodes { fmt.Println(node) } }, }, { Name: "manage", ShortName: "m", Usage: "manage a docker cluster", Flags: []cli.Flag{ flStore, flStrategy, flFilter, flHosts, flHeartBeat, flOverCommit, flTls, flTlsCaCert, flTlsCert, flTlsKey, flTlsVerify, flEnableCors}, Action: manage, }, { Name: "join", ShortName: "j", Usage: "join a docker cluster", Flags: []cli.Flag{flAddr, flHeartBeat}, Action: join, }, } if err := app.Run(os.Args); err != nil { log.Fatal(err) } }
func manage(c *cli.Context) { var ( tlsConfig *tls.Config = nil err error ) // If either --tls or --tlsverify are specified, load the certificates. if c.Bool("tls") || c.Bool("tlsverify") { if !c.IsSet("tlscert") || !c.IsSet("tlskey") { log.Fatal("--tlscert and --tlskey must be provided when using --tls") } if c.Bool("tlsverify") && !c.IsSet("tlscacert") { log.Fatal("--tlscacert must be provided when using --tlsverify") } tlsConfig, err = loadTlsConfig( c.String("tlscacert"), c.String("tlscert"), c.String("tlskey"), c.Bool("tlsverify")) if err != nil { log.Fatal(err) } } else { // Otherwise, if neither --tls nor --tlsverify are specified, abort if // the other flags are passed as they will be ignored. if c.IsSet("tlscert") || c.IsSet("tlskey") || c.IsSet("tlscacert") { log.Fatal("--tlscert, --tlskey and --tlscacert require the use of either --tls or --tlsverify") } } store := state.NewStore(path.Join(c.String("rootdir"), "state")) if err := store.Initialize(); err != nil { log.Fatal(err) } cluster := cluster.NewCluster(store, tlsConfig, c.Float64("overcommit")) cluster.Events(&logHandler{}) dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to manage a cluster. See '%s manage --help'.", c.App.Name) } s, err := strategy.New(c.String("strategy")) if err != nil { log.Fatal(err) } // see https://github.com/codegangsta/cli/issues/160 names := c.StringSlice("filter") if c.IsSet("filter") || c.IsSet("f") { names = names[DEFAULT_FILTER_NUMBER:] } fs, err := filter.New(names) if err != nil { log.Fatal(err) } // get the list of nodes from the discovery service go func() { d, err := discovery.New(dflag, c.Int("heartbeat")) if err != nil { log.Fatal(err) } nodes, err := d.Fetch() if err != nil { log.Fatal(err) } cluster.UpdateNodes(nodes) go d.Watch(cluster.UpdateNodes) }() sched := scheduler.NewScheduler( cluster, s, fs, ) // see https://github.com/codegangsta/cli/issues/160 hosts := c.StringSlice("host") if c.IsSet("host") || c.IsSet("H") { hosts = hosts[1:] } log.Fatal(api.ListenAndServe(cluster, sched, hosts, c.Bool("cors"), tlsConfig)) }
func TestNew(t *testing.T) { d, err := discovery.New("file:///path/to/file", 0, 0, nil) assert.NoError(t, err) assert.Equal(t, d.(*Discovery).path, "/path/to/file") }
// Run the Swarm CLI. func Run() { app := cli.NewApp() app.Name = path.Base(os.Args[0]) app.Usage = "A Docker-native clustering system" app.Version = version.VERSION + " (" + version.GITCOMMIT + ")" app.Author = "" app.Email = "" app.Flags = []cli.Flag{ cli.BoolFlag{ Name: "debug", Usage: "debug mode", EnvVar: "DEBUG", }, cli.StringFlag{ Name: "log-level, l", Value: "info", Usage: fmt.Sprintf("Log level (options: debug, info, warn, error, fatal, panic)"), }, } // logs app.Before = func(c *cli.Context) error { log.SetOutput(os.Stderr) level, err := log.ParseLevel(c.String("log-level")) if err != nil { log.Fatalf(err.Error()) } log.SetLevel(level) // If a log level wasn't specified and we are running in debug mode, // enforce log-level=debug. if !c.IsSet("log-level") && !c.IsSet("l") && c.Bool("debug") { log.SetLevel(log.DebugLevel) } return nil } app.Commands = []cli.Command{ { Name: "create", ShortName: "c", Usage: "Create a cluster", Action: func(c *cli.Context) { if len(c.Args()) != 0 { log.Fatalf("the `create` command takes no arguments. See '%s create --help'.", c.App.Name) } discovery := &token.Discovery{} discovery.Initialize("", 0, 0) token, err := discovery.CreateCluster() if err != nil { log.Fatal(err) } fmt.Println(token) }, }, { Name: "list", ShortName: "l", Usage: "List nodes in a cluster", Flags: []cli.Flag{flTimeout}, Action: func(c *cli.Context) { dflag := getDiscovery(c) if dflag == "" { log.Fatalf("discovery required to list a cluster. See '%s list --help'.", c.App.Name) } timeout, err := time.ParseDuration(c.String("timeout")) if err != nil { log.Fatalf("invalid --timeout: %v", err) } d, err := discovery.New(dflag, timeout, 0) if err != nil { log.Fatal(err) } ch, errCh := d.Watch(nil) select { case entries := <-ch: for _, entry := range entries { fmt.Println(entry) } case err := <-errCh: log.Fatal(err) case <-time.After(timeout): log.Fatal("Timed out") } }, }, { Name: "manage", ShortName: "m", Usage: "Manage a docker cluster", Flags: []cli.Flag{ flStore, flStrategy, flFilter, flHosts, flLeaderElection, flAddr, flTLS, flTLSCaCert, flTLSCert, flTLSKey, flTLSVerify, flHeartBeat, flEnableCors, flCluster, flClusterOpt}, Action: manage, }, { Name: "join", ShortName: "j", Usage: "join a docker cluster", Flags: []cli.Flag{flAddr, flHeartBeat, flTTL}, Action: join, }, } if err := app.Run(os.Args); err != nil { log.Fatal(err) } }
// Create a new endpoint func newDiscoveryEndpoint(url, localAddr string, discPort string, weaveCli *WeaveClient, heartbeat time.Duration, ttl time.Duration) (*discoveryEndpoint, error) { d, err := discovery.New(url, heartbeat, ttl) if err != nil { return nil, err } stopChan := make(chan struct{}) ep := discoveryEndpoint{ Discovery: d, url: url, stopChan: stopChan, } register := func() {} var localAddrHost, localAddrPort string if len(localAddr) > 0 { localAddrHost, localAddrPort, err = net.SplitHostPort(localAddr) if err != nil { Log.Warningf("[manager] Invalid local address '%s': %s", localAddr, err) return nil, err } register = func() { Log.Debugf("[manager] Registering on '%s' we are at '%s' (%s period)...", url, localAddr, heartbeat) if err := d.Register(localAddr); err != nil { Log.Warningf("[manager] Registration failed: %s", err) } else { ep.lastRegister = time.Now() } } } hostAndPort := func(e *discovery.Entry) (string, string, bool) { host := e.Host port := e.Port if len(discPort) > 0 { port = discPort } return host, port, (host == localAddrHost && port == localAddrPort) } entriesChan, errorsChan := d.Watch(stopChan) ticker := time.NewTicker(heartbeat) go func() { register() currentEntries := discovery.Entries{} for { select { case reportedEntries := <-entriesChan: added, removed := currentEntries.Diff(reportedEntries) ep.added += uint64(len(added)) ep.removed += uint64(len(removed)) currentEntries = reportedEntries Log.Printf("[manager] Updates from '%s': %d added, %d removed...", url, len(added), len(removed)) for _, e := range added { if host, port, isLocal := hostAndPort(e); !isLocal { weaveCli.Join(host, port) } } for _, e := range removed { if host, port, isLocal := hostAndPort(e); !isLocal { weaveCli.Forget(host, port) } } case reportedError := <-errorsChan: Log.Warningf("[manager] Error from endpoint %s: %s...", url, reportedError) case <-ticker.C: register() case <-stopChan: ticker.Stop() return } } }() return &ep, nil }