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 }