123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- package power
- import (
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "syscall"
- "time"
- "github.com/rancher/os/cmd/control/install"
- "github.com/rancher/os/config"
- "github.com/rancher/os/pkg/docker"
- "github.com/rancher/os/pkg/log"
- "github.com/rancher/os/pkg/util"
- "github.com/docker/engine-api/types"
- "github.com/docker/engine-api/types/container"
- "github.com/docker/engine-api/types/filters"
- "golang.org/x/net/context"
- )
- // You can't shutdown the system from a process in console because we want to stop the console container.
- // If you do that you kill yourself. So we spawn a separate container to do power operations
- // This can up because on shutdown we want ssh to gracefully die, terminating ssh connections and not just hanging tcp session
- //
- // Be careful of container name. only [a-zA-Z0-9][a-zA-Z0-9_.-] are allowed
- func runDocker(name string) error {
- if os.ExpandEnv("${IN_DOCKER}") == "true" {
- return nil
- }
- client, err := docker.NewSystemClient()
- if err != nil {
- return err
- }
- cmd := os.Args
- log.Debugf("runDocker cmd: %s", cmd)
- if name == "" {
- name = filepath.Base(os.Args[0])
- }
- containerName := strings.TrimPrefix(strings.Join(strings.Split(name, "/"), "-"), "-")
- existing, err := client.ContainerInspect(context.Background(), containerName)
- if err == nil && existing.ID != "" {
- // remove the old version of reboot
- err := client.ContainerRemove(context.Background(), types.ContainerRemoveOptions{
- ContainerID: existing.ID,
- })
- if err != nil {
- return err
- }
- }
- currentContainerID, err := util.GetCurrentContainerID()
- if err != nil {
- return err
- }
- currentContainer, err := client.ContainerInspect(context.Background(), currentContainerID)
- if err != nil {
- return err
- }
- powerContainer, err := client.ContainerCreate(context.Background(),
- &container.Config{
- Image: currentContainer.Config.Image,
- Cmd: cmd,
- Env: []string{
- "IN_DOCKER=true",
- },
- },
- &container.HostConfig{
- PidMode: "host",
- NetworkMode: "none",
- VolumesFrom: []string{
- currentContainer.ID,
- },
- Privileged: true,
- }, nil, containerName)
- if err != nil {
- return err
- }
- err = client.ContainerStart(context.Background(), powerContainer.ID)
- if err != nil {
- return err
- }
- reader, err := client.ContainerLogs(context.Background(), types.ContainerLogsOptions{
- ContainerID: powerContainer.ID,
- ShowStderr: true,
- ShowStdout: true,
- Follow: true,
- })
- if err != nil {
- log.Fatal(err)
- }
- for {
- p := make([]byte, 4096)
- n, err := reader.Read(p)
- if err != nil {
- log.Error(err)
- if n == 0 {
- reader.Close()
- break
- }
- }
- if n > 0 {
- fmt.Print(string(p))
- }
- }
- if err != nil {
- log.Fatal(err)
- }
- os.Exit(0)
- return nil
- }
- func reboot(name string, force bool, code uint) {
- if os.Geteuid() != 0 {
- log.Fatalf("%s: Need to be root", os.Args[0])
- }
- // Add shutdown timeout
- cfg := config.LoadConfig()
- timeoutValue := cfg.Rancher.ShutdownTimeout
- if timeoutValue == 0 {
- timeoutValue = 60
- }
- if timeoutValue < 5 {
- timeoutValue = 5
- }
- log.Infof("Setting %s timeout to %d (rancher.shutdown_timeout set to %d)", os.Args[0], timeoutValue, cfg.Rancher.ShutdownTimeout)
- go func() {
- timeout := time.After(time.Duration(timeoutValue) * time.Second)
- tick := time.Tick(100 * time.Millisecond)
- // Keep trying until we're timed out or got a result or got an error
- for {
- select {
- // Got a timeout! fail with a timeout error
- case <-timeout:
- log.Errorf("Container shutdown taking too long, forcing %s.", os.Args[0])
- syscall.Sync()
- syscall.Reboot(int(code))
- case <-tick:
- fmt.Printf(".")
- }
- }
- }()
- // reboot -f should work even when system-docker is having problems
- if !force {
- if kexecFlag || previouskexecFlag || kexecAppendFlag != "" {
- // pass through the cmdline args
- name = ""
- }
- if err := runDocker(name); err != nil {
- log.Fatal(err)
- }
- }
- if kexecFlag || previouskexecFlag || kexecAppendFlag != "" {
- // need to mount boot dir, or `system-docker run -v /:/host -w /host/boot` ?
- baseName := "/mnt/new_img"
- _, _, err := install.MountDevice(baseName, "", "", false)
- if err != nil {
- log.Errorf("ERROR: can't Kexec: %s", err)
- return
- }
- defer util.Unmount(baseName)
- Kexec(previouskexecFlag, filepath.Join(baseName, config.BootDir), kexecAppendFlag)
- return
- }
- if !force {
- err := shutDownContainers()
- if err != nil {
- log.Error(err)
- }
- }
- syscall.Sync()
- err := syscall.Reboot(int(code))
- if err != nil {
- log.Fatal(err)
- }
- }
- func shutDownContainers() error {
- var err error
- shutDown := true
- timeout := 2
- for i, arg := range os.Args {
- if arg == "-f" || arg == "--f" || arg == "--force" {
- shutDown = false
- }
- if arg == "-t" || arg == "--t" || arg == "--timeout" {
- if len(os.Args) > i+1 {
- t, err := strconv.Atoi(os.Args[i+1])
- if err != nil {
- return err
- }
- timeout = t
- } else {
- log.Error("please specify a timeout")
- }
- }
- }
- if !shutDown {
- return nil
- }
- client, err := docker.NewSystemClient()
- if err != nil {
- return err
- }
- filter := filters.NewArgs()
- filter.Add("status", "running")
- opts := types.ContainerListOptions{
- All: true,
- Filter: filter,
- }
- containers, err := client.ContainerList(context.Background(), opts)
- if err != nil {
- return err
- }
- currentContainerID, err := util.GetCurrentContainerID()
- if err != nil {
- return err
- }
- var stopErrorStrings []string
- consoleContainerIdx := -1
- for idx, container := range containers {
- if container.ID == currentContainerID {
- continue
- }
- if container.Names[0] == "/console" {
- consoleContainerIdx = idx
- continue
- }
- log.Infof("Stopping %s : %s", container.Names[0], container.ID[:12])
- stopErr := client.ContainerStop(context.Background(), container.ID, timeout)
- if stopErr != nil {
- log.Errorf("------- Error Stopping %s : %s", container.Names[0], stopErr.Error())
- stopErrorStrings = append(stopErrorStrings, " ["+container.ID+"] "+stopErr.Error())
- }
- }
- // lets see what containers are still running and only wait on those
- containers, err = client.ContainerList(context.Background(), opts)
- if err != nil {
- return err
- }
- var waitErrorStrings []string
- for idx, container := range containers {
- if container.ID == currentContainerID {
- continue
- }
- if container.Names[0] == "/console" {
- consoleContainerIdx = idx
- continue
- }
- log.Infof("Waiting %s : %s", container.Names[0], container.ID[:12])
- _, waitErr := client.ContainerWait(context.Background(), container.ID)
- if waitErr != nil {
- log.Errorf("------- Error Waiting %s : %s", container.Names[0], waitErr.Error())
- waitErrorStrings = append(waitErrorStrings, " ["+container.ID+"] "+waitErr.Error())
- }
- }
- // and now stop the console
- if consoleContainerIdx != -1 {
- container := containers[consoleContainerIdx]
- log.Infof("Console Stopping %v : %s", container.Names, container.ID[:12])
- stopErr := client.ContainerStop(context.Background(), container.ID, timeout)
- if stopErr != nil {
- log.Errorf("------- Error Stopping %v : %s", container.Names, stopErr.Error())
- stopErrorStrings = append(stopErrorStrings, " ["+container.ID+"] "+stopErr.Error())
- }
- log.Infof("Console Waiting %v : %s", container.Names, container.ID[:12])
- _, waitErr := client.ContainerWait(context.Background(), container.ID)
- if waitErr != nil {
- log.Errorf("------- Error Waiting %v : %s", container.Names, waitErr.Error())
- waitErrorStrings = append(waitErrorStrings, " ["+container.ID+"] "+waitErr.Error())
- }
- }
- if len(waitErrorStrings) != 0 || len(stopErrorStrings) != 0 {
- return errors.New("error while stopping \n1. STOP Errors [" + strings.Join(stopErrorStrings, ",") + "] \n2. WAIT Errors [" + strings.Join(waitErrorStrings, ",") + "]")
- }
- return nil
- }
|