github.com/technosophos/deis@v1.7.1-0.20150915173815-f9005256004b/deisctl/cmd/cmd.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/deis/deis/deisctl/backend"
    15  	"github.com/deis/deis/deisctl/config"
    16  	"github.com/deis/deis/deisctl/units"
    17  	"github.com/deis/deis/deisctl/utils"
    18  	"github.com/deis/deis/deisctl/utils/net"
    19  )
    20  
    21  const (
    22  	// PlatformCommand is shorthand for "all the Deis components."
    23  	PlatformCommand string = "platform"
    24  	// StatelessPlatformCommand is shorthand for the components except store-*, database, and logger.
    25  	StatelessPlatformCommand string = "stateless-platform"
    26  	swarm                    string = "swarm"
    27  	mesos                    string = "mesos"
    28  	// DefaultRouterMeshSize defines the default number of routers to be loaded when installing the platform.
    29  	DefaultRouterMeshSize uint8  = 3
    30  	k8s                   string = "k8s"
    31  )
    32  
    33  // ListUnits prints a list of installed units.
    34  func ListUnits(b backend.Backend) error {
    35  	return b.ListUnits()
    36  }
    37  
    38  // ListUnitFiles prints the contents of all defined unit files.
    39  func ListUnitFiles(b backend.Backend) error {
    40  	return b.ListUnitFiles()
    41  }
    42  
    43  // Location to write standard output. By default, this is the os.Stdout.
    44  var Stdout io.Writer = os.Stdout
    45  
    46  // Location to write standard error information. By default, this is the os.Stderr.
    47  var Stderr io.Writer = os.Stderr
    48  
    49  // Number of routers to be installed. By default, it's DefaultRouterMeshSize.
    50  var RouterMeshSize = DefaultRouterMeshSize
    51  
    52  // Scale grows or shrinks the number of running components.
    53  // Currently "router", "registry" and "store-gateway" are the only types that can be scaled.
    54  func Scale(targets []string, b backend.Backend) error {
    55  	var wg sync.WaitGroup
    56  
    57  	for _, target := range targets {
    58  		component, num, err := splitScaleTarget(target)
    59  		if err != nil {
    60  			return err
    61  		}
    62  		// the router, registry, and store-gateway are the only component that can scale at the moment
    63  		if !strings.Contains(component, "router") && !strings.Contains(component, "registry") && !strings.Contains(component, "store-gateway") {
    64  			return fmt.Errorf("cannot scale %s component", component)
    65  		}
    66  		b.Scale(component, num, &wg, Stdout, Stderr)
    67  		wg.Wait()
    68  	}
    69  	return nil
    70  }
    71  
    72  // Start activates the specified components.
    73  func Start(targets []string, b backend.Backend) error {
    74  
    75  	// if target is platform, install all services
    76  	if len(targets) == 1 {
    77  		switch targets[0] {
    78  		case PlatformCommand:
    79  			return StartPlatform(b, false)
    80  		case StatelessPlatformCommand:
    81  			return StartPlatform(b, true)
    82  		case mesos:
    83  			return StartMesos(b)
    84  		case swarm:
    85  			return StartSwarm(b)
    86  		case k8s:
    87  			return StartK8s(b)
    88  		}
    89  	}
    90  	var wg sync.WaitGroup
    91  
    92  	b.Start(targets, &wg, Stdout, Stderr)
    93  	wg.Wait()
    94  
    95  	return nil
    96  }
    97  
    98  // RollingRestart restart instance unit in a rolling manner
    99  func RollingRestart(target string, b backend.Backend) error {
   100  	var wg sync.WaitGroup
   101  
   102  	b.RollingRestart(target, &wg, Stdout, Stderr)
   103  	wg.Wait()
   104  
   105  	return nil
   106  }
   107  
   108  // CheckRequiredKeys exist in config backend
   109  func CheckRequiredKeys(cb config.Backend) error {
   110  	if err := config.CheckConfig("/deis/platform/", "domain", cb); err != nil {
   111  		return fmt.Errorf(`Missing platform domain, use:
   112  deisctl config platform set domain=<your-domain>`)
   113  	}
   114  
   115  	if err := config.CheckConfig("/deis/platform/", "sshPrivateKey", cb); err != nil {
   116  		fmt.Printf(`Warning: Missing sshPrivateKey, "deis run" will be unavailable. Use:
   117  deisctl config platform set sshPrivateKey=<path-to-key>
   118  `)
   119  	}
   120  	return nil
   121  }
   122  
   123  func startDefaultServices(b backend.Backend, stateless bool, wg *sync.WaitGroup, out, err io.Writer) {
   124  
   125  	// Wait for groups to come up.
   126  	// If we're running in stateless mode, we start only a subset of services.
   127  	if !stateless {
   128  		fmt.Fprintln(out, "Storage subsystem...")
   129  		b.Start([]string{"store-monitor"}, wg, out, err)
   130  		wg.Wait()
   131  		b.Start([]string{"store-daemon"}, wg, out, err)
   132  		wg.Wait()
   133  		b.Start([]string{"store-metadata"}, wg, out, err)
   134  		wg.Wait()
   135  
   136  		// we start gateway first to give metadata time to come up for volume
   137  		b.Start([]string{"store-gateway@*"}, wg, out, err)
   138  		wg.Wait()
   139  		b.Start([]string{"store-volume"}, wg, out, err)
   140  		wg.Wait()
   141  	}
   142  
   143  	// start logging subsystem first to collect logs from other components
   144  	fmt.Fprintln(out, "Logging subsystem...")
   145  	if !stateless {
   146  		b.Start([]string{"logger"}, wg, out, err)
   147  		wg.Wait()
   148  	}
   149  	b.Start([]string{"logspout"}, wg, out, err)
   150  	wg.Wait()
   151  
   152  	// Start these in parallel. This section can probably be removed now.
   153  	var bgwg sync.WaitGroup
   154  	var trash bytes.Buffer
   155  	batch := []string{
   156  		"database", "registry@*", "controller", "builder",
   157  		"publisher", "router@*",
   158  	}
   159  	if stateless {
   160  		batch = []string{"registry@*", "controller", "builder", "publisher", "router@*"}
   161  	}
   162  	b.Start(batch, &bgwg, &trash, &trash)
   163  	// End background stuff.
   164  
   165  	fmt.Fprintln(Stdout, "Control plane...")
   166  	batch = []string{"database", "registry@*", "controller"}
   167  	if stateless {
   168  		batch = []string{"registry@*", "controller"}
   169  	}
   170  	b.Start(batch, wg, out, err)
   171  	wg.Wait()
   172  
   173  	b.Start([]string{"builder"}, wg, out, err)
   174  	wg.Wait()
   175  
   176  	fmt.Fprintln(out, "Data plane...")
   177  	b.Start([]string{"publisher"}, wg, out, err)
   178  	wg.Wait()
   179  
   180  	fmt.Fprintln(out, "Router mesh...")
   181  	b.Start([]string{"router@*"}, wg, out, err)
   182  	wg.Wait()
   183  }
   184  
   185  // Stop deactivates the specified components.
   186  func Stop(targets []string, b backend.Backend) error {
   187  
   188  	// if target is platform, stop all services
   189  	if len(targets) == 1 {
   190  		switch targets[0] {
   191  		case PlatformCommand:
   192  			return StopPlatform(b, false)
   193  		case StatelessPlatformCommand:
   194  			return StopPlatform(b, true)
   195  		case mesos:
   196  			return StopMesos(b)
   197  		case swarm:
   198  			return StopSwarm(b)
   199  		case k8s:
   200  			return StopK8s(b)
   201  		}
   202  	}
   203  
   204  	var wg sync.WaitGroup
   205  
   206  	b.Stop(targets, &wg, Stdout, Stderr)
   207  	wg.Wait()
   208  
   209  	return nil
   210  }
   211  
   212  func stopDefaultServices(b backend.Backend, stateless bool, wg *sync.WaitGroup, out, err io.Writer) {
   213  
   214  	fmt.Fprintln(out, "Router mesh...")
   215  	b.Stop([]string{"router@*"}, wg, out, err)
   216  	wg.Wait()
   217  
   218  	fmt.Fprintln(out, "Data plane...")
   219  	b.Stop([]string{"publisher"}, wg, out, err)
   220  	wg.Wait()
   221  
   222  	fmt.Fprintln(out, "Control plane...")
   223  	if stateless {
   224  		b.Stop([]string{"controller", "builder", "registry@*"}, wg, out, err)
   225  	} else {
   226  		b.Stop([]string{"controller", "builder", "database", "registry@*"}, wg, out, err)
   227  	}
   228  	wg.Wait()
   229  
   230  	fmt.Fprintln(out, "Logging subsystem...")
   231  	if stateless {
   232  		b.Stop([]string{"logspout"}, wg, out, err)
   233  	} else {
   234  		b.Stop([]string{"logger", "logspout"}, wg, out, err)
   235  	}
   236  	wg.Wait()
   237  
   238  	if !stateless {
   239  		fmt.Fprintln(out, "Storage subsystem...")
   240  		b.Stop([]string{"store-volume", "store-gateway@*"}, wg, out, err)
   241  		wg.Wait()
   242  		b.Stop([]string{"store-metadata"}, wg, out, err)
   243  		wg.Wait()
   244  		b.Stop([]string{"store-daemon"}, wg, out, err)
   245  		wg.Wait()
   246  		b.Stop([]string{"store-monitor"}, wg, out, err)
   247  		wg.Wait()
   248  	}
   249  
   250  }
   251  
   252  // Restart stops and then starts the specified components.
   253  func Restart(targets []string, b backend.Backend) error {
   254  
   255  	// act as if the user called "stop" and then "start"
   256  	if err := Stop(targets, b); err != nil {
   257  		return err
   258  	}
   259  
   260  	return Start(targets, b)
   261  }
   262  
   263  // Status prints the current status of components.
   264  func Status(targets []string, b backend.Backend) error {
   265  
   266  	for _, target := range targets {
   267  		if err := b.Status(target); err != nil {
   268  			return err
   269  		}
   270  	}
   271  	return nil
   272  }
   273  
   274  // Journal prints log output for the specified components.
   275  func Journal(targets []string, b backend.Backend) error {
   276  
   277  	for _, target := range targets {
   278  		if err := b.Journal(target); err != nil {
   279  			return err
   280  		}
   281  	}
   282  	return nil
   283  }
   284  
   285  // Install loads the definitions of components from local unit files.
   286  // After Install, the components will be available to Start.
   287  func Install(targets []string, b backend.Backend, cb config.Backend, checkKeys func(config.Backend) error) error {
   288  
   289  	// if target is platform, install all services
   290  	if len(targets) == 1 {
   291  		switch targets[0] {
   292  		case PlatformCommand:
   293  			return InstallPlatform(b, cb, checkKeys, false)
   294  		case StatelessPlatformCommand:
   295  			return InstallPlatform(b, cb, checkKeys, true)
   296  		case mesos:
   297  			return InstallMesos(b)
   298  		case swarm:
   299  			return InstallSwarm(b)
   300  		case k8s:
   301  			return InstallK8s(b)
   302  		}
   303  	}
   304  	var wg sync.WaitGroup
   305  
   306  	// otherwise create the specific targets
   307  	b.Create(targets, &wg, Stdout, Stderr)
   308  	wg.Wait()
   309  
   310  	return nil
   311  }
   312  
   313  func installDefaultServices(b backend.Backend, stateless bool, wg *sync.WaitGroup, out, err io.Writer) {
   314  
   315  	if !stateless {
   316  		fmt.Fprintln(out, "Storage subsystem...")
   317  		b.Create([]string{"store-daemon", "store-monitor", "store-metadata", "store-volume", "store-gateway@1"}, wg, out, err)
   318  		wg.Wait()
   319  	}
   320  
   321  	fmt.Fprintln(out, "Logging subsystem...")
   322  	if stateless {
   323  		b.Create([]string{"logspout"}, wg, out, err)
   324  	} else {
   325  		b.Create([]string{"logger", "logspout"}, wg, out, err)
   326  	}
   327  	wg.Wait()
   328  
   329  	fmt.Fprintln(out, "Control plane...")
   330  	if stateless {
   331  		b.Create([]string{"registry@1", "controller", "builder"}, wg, out, err)
   332  	} else {
   333  		b.Create([]string{"database", "registry@1", "controller", "builder"}, wg, out, err)
   334  	}
   335  	wg.Wait()
   336  
   337  	fmt.Fprintln(out, "Data plane...")
   338  	b.Create([]string{"publisher"}, wg, out, err)
   339  	wg.Wait()
   340  
   341  	fmt.Fprintln(out, "Router mesh...")
   342  	b.Create(getRouters(), wg, out, err)
   343  	wg.Wait()
   344  
   345  }
   346  
   347  func getRouters() []string {
   348  	routers := make([]string, RouterMeshSize)
   349  	for i := uint8(0); i < RouterMeshSize; i++ {
   350  		routers[i] = fmt.Sprintf("router@%d", i+1)
   351  	}
   352  	return routers
   353  }
   354  
   355  // Uninstall unloads the definitions of the specified components.
   356  // After Uninstall, the components will be unavailable until Install is called.
   357  func Uninstall(targets []string, b backend.Backend) error {
   358  	if len(targets) == 1 {
   359  		switch targets[0] {
   360  		case PlatformCommand:
   361  			return UninstallPlatform(b, false)
   362  		case StatelessPlatformCommand:
   363  			return UninstallPlatform(b, true)
   364  		case mesos:
   365  			return UninstallMesos(b)
   366  		case swarm:
   367  			return UnInstallSwarm(b)
   368  		case k8s:
   369  			return UnInstallK8s(b)
   370  		}
   371  	}
   372  
   373  	var wg sync.WaitGroup
   374  
   375  	// uninstall the specific target
   376  	b.Destroy(targets, &wg, Stdout, Stderr)
   377  	wg.Wait()
   378  
   379  	return nil
   380  }
   381  
   382  func uninstallAllServices(b backend.Backend, stateless bool, wg *sync.WaitGroup, out, err io.Writer) error {
   383  
   384  	fmt.Fprintln(out, "Router mesh...")
   385  	b.Destroy([]string{"router@*"}, wg, out, err)
   386  	wg.Wait()
   387  
   388  	fmt.Fprintln(out, "Data plane...")
   389  	b.Destroy([]string{"publisher"}, wg, out, err)
   390  	wg.Wait()
   391  
   392  	fmt.Fprintln(out, "Control plane...")
   393  	if stateless {
   394  		b.Destroy([]string{"controller", "builder", "registry@*"}, wg, out, err)
   395  	} else {
   396  		b.Destroy([]string{"controller", "builder", "database", "registry@*"}, wg, out, err)
   397  	}
   398  	wg.Wait()
   399  
   400  	fmt.Fprintln(out, "Logging subsystem...")
   401  	if stateless {
   402  		b.Destroy([]string{"logspout"}, wg, out, err)
   403  	} else {
   404  		b.Destroy([]string{"logger", "logspout"}, wg, out, err)
   405  	}
   406  	wg.Wait()
   407  
   408  	if !stateless {
   409  		fmt.Fprintln(out, "Storage subsystem...")
   410  		b.Destroy([]string{"store-volume", "store-gateway@*"}, wg, out, err)
   411  		wg.Wait()
   412  		b.Destroy([]string{"store-metadata"}, wg, out, err)
   413  		wg.Wait()
   414  		b.Destroy([]string{"store-daemon"}, wg, out, err)
   415  		wg.Wait()
   416  		b.Destroy([]string{"store-monitor"}, wg, out, err)
   417  		wg.Wait()
   418  	}
   419  
   420  	return nil
   421  }
   422  
   423  func splitScaleTarget(target string) (c string, num int, err error) {
   424  	r := regexp.MustCompile(`([a-z-]+)=([\d]+)`)
   425  	match := r.FindStringSubmatch(target)
   426  	if len(match) == 0 {
   427  		err = fmt.Errorf("Could not parse: %v", target)
   428  		return
   429  	}
   430  	c = match[1]
   431  	num, err = strconv.Atoi(match[2])
   432  	if err != nil {
   433  		return
   434  	}
   435  	return
   436  }
   437  
   438  // Config gets or sets a configuration value from the cluster.
   439  //
   440  // A configuration value is stored and retrieved from a key/value store
   441  // at /deis/<component>/<config>. Configuration values are typically used for component-level
   442  // configuration, such as enabling TLS for the routers.
   443  func Config(target string, action string, key []string, cb config.Backend) error {
   444  	if err := config.Config(target, action, key, cb); err != nil {
   445  		return err
   446  	}
   447  	return nil
   448  }
   449  
   450  // RefreshUnits overwrites local unit files with those requested.
   451  // Downloading from the Deis project GitHub URL by tag or SHA is the only mechanism
   452  // currently supported.
   453  func RefreshUnits(unitDir, tag, rootURL string) error {
   454  	unitDir = utils.ResolvePath(unitDir)
   455  	decoratorDir := filepath.Join(unitDir, "decorators")
   456  	// create the target dir if necessary
   457  	if err := os.MkdirAll(decoratorDir, 0755); err != nil {
   458  		return err
   459  	}
   460  	// download and save the unit files to the specified path
   461  	for _, unit := range units.Names {
   462  		unitSrc := rootURL + tag + "/deisctl/units/" + unit + ".service"
   463  		unitDest := filepath.Join(unitDir, unit+".service")
   464  		if err := net.Download(unitSrc, unitDest); err != nil {
   465  			return err
   466  		}
   467  		fmt.Printf("Refreshed %s unit from %s\n", unit, tag)
   468  		decoratorSrc := rootURL + tag + "/deisctl/units/decorators/" + unit + ".service.decorator"
   469  		decoratorDest := filepath.Join(decoratorDir, unit+".service.decorator")
   470  		if err := net.Download(decoratorSrc, decoratorDest); err != nil {
   471  			if err.Error() == "404 Not Found" {
   472  				fmt.Printf("Decorator for %s not found in %s\n", unit, tag)
   473  			} else {
   474  				return err
   475  			}
   476  		} else {
   477  			fmt.Printf("Refreshed %s decorator from %s\n", unit, tag)
   478  		}
   479  	}
   480  	return nil
   481  }
   482  
   483  // SSH opens an interactive shell on a machine in the cluster
   484  func SSH(target string, cmd []string, b backend.Backend) error {
   485  
   486  	if len(cmd) > 0 {
   487  		return b.SSHExec(target, strings.Join(cmd, " "))
   488  	}
   489  
   490  	return b.SSH(target)
   491  }
   492  
   493  // Dock connects to the appropriate host and runs 'docker exec -it'.
   494  func Dock(target string, cmd []string, b backend.Backend) error {
   495  	return b.Dock(target, cmd)
   496  }