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
}
Exemple #3
0
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")
	}
}
Exemple #5
0
// 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
}
Exemple #6
0
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)
		}
	}
}
Exemple #7
0
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)
	}
}
Exemple #8
0
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)
	}
}
Exemple #9
0
// 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
}
Exemple #10
0
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)
	}
}
Exemple #11
0
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")
	}
}
Exemple #12
0
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)
	}
}
Exemple #13
0
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))
}
Exemple #14
0
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")
}
Exemple #15
0
// 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)
	}
}
Exemple #16
0
// 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
}