2015-02-12 23:22:48 +00:00
|
|
|
package power
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2017-07-28 03:27:40 +00:00
|
|
|
"fmt"
|
2015-02-12 23:22:48 +00:00
|
|
|
"os"
|
2015-03-15 04:31:11 +00:00
|
|
|
"path/filepath"
|
2015-02-12 23:22:48 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2015-02-17 05:09:26 +00:00
|
|
|
"syscall"
|
2017-07-28 03:27:40 +00:00
|
|
|
"time"
|
2015-02-12 23:22:48 +00:00
|
|
|
|
2017-07-06 01:37:18 +00:00
|
|
|
"github.com/rancher/os/cmd/control/install"
|
2017-07-28 03:27:40 +00:00
|
|
|
"github.com/rancher/os/config"
|
2018-09-16 04:55:26 +00:00
|
|
|
"github.com/rancher/os/pkg/docker"
|
2018-10-23 04:13:32 +00:00
|
|
|
"github.com/rancher/os/pkg/log"
|
2018-09-16 04:55:26 +00:00
|
|
|
"github.com/rancher/os/pkg/util"
|
2018-10-23 04:13:32 +00:00
|
|
|
|
|
|
|
"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"
|
2015-02-12 23:22:48 +00:00
|
|
|
)
|
|
|
|
|
2017-07-05 04:14:22 +00:00
|
|
|
// 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
|
2017-12-15 08:15:13 +00:00
|
|
|
//
|
|
|
|
// Be careful of container name. only [a-zA-Z0-9][a-zA-Z0-9_.-] are allowed
|
2015-03-19 22:36:40 +00:00
|
|
|
func runDocker(name string) error {
|
2015-03-15 04:31:11 +00:00
|
|
|
if os.ExpandEnv("${IN_DOCKER}") == "true" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := docker.NewSystemClient()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
cmd/power: Set correct container name and ensure full command executed
This fixes a few issues that are preventing shutdown and friends from
behaving correctly:
* The command name, which is being used to determine via what command it
was being called (ie: shutdown, reboot, or halt) was not being parsed
for absolute paths. This was preventing certain logic from being handled
(example: enforcing a static time value of "now" for shutdown), but more
problematically was the fact that it was being used as the container
name being passed to runDocker, the function that launches the
independent shutdown container. This was causing the shutdown container
to fail as something like "/sbin/shutdown" is not a valid name for a
container. The logic to parse out the base command being run is actually
present in runDocker, but does not run if a name is supplied to the
function.
* Further, the command line was not being parsed in the shutdown
container if the name supplied to runDocker was non-empty. Rather, the
full command to run just became the name of the container. Hence,
something like "/sbin/shutdown -h now" became just "shutdown", executing
the default power off behaviour for all actions (including reboots).
* Further to this, open-vm-tools expects "/sbin/shutdown -h now" to be a
valid command to halt the system, which was not being recognized as the
only recognized short-form halt flag in shutdown was its capital version
(-H).
This fixes these three issues by parsing out the base of the called
command before sending it to reboot, using all of os.Argv as the command
line to run regardless of if a name was set for the container or not,
and finally adding the lowercase -h switch to the "shutdown" form of
this command ("halt" is still uppercase only).
Fixes rancher/os#2121.
Fixes rancher/os#2074.
2017-10-21 00:09:13 +00:00
|
|
|
cmd := os.Args
|
2018-01-25 08:25:09 +00:00
|
|
|
log.Debugf("runDocker cmd: %s", cmd)
|
2015-03-19 22:36:40 +00:00
|
|
|
|
|
|
|
if name == "" {
|
|
|
|
name = filepath.Base(os.Args[0])
|
|
|
|
}
|
2018-01-23 10:09:59 +00:00
|
|
|
|
|
|
|
containerName := strings.TrimPrefix(strings.Join(strings.Split(name, "/"), "-"), "-")
|
2015-03-19 22:36:40 +00:00
|
|
|
|
2017-12-15 08:15:13 +00:00
|
|
|
existing, err := client.ContainerInspect(context.Background(), containerName)
|
2016-05-24 00:21:28 +00:00
|
|
|
if err == nil && existing.ID != "" {
|
2016-06-01 01:12:52 +00:00
|
|
|
err := client.ContainerRemove(context.Background(), types.ContainerRemoveOptions{
|
|
|
|
ContainerID: existing.ID,
|
|
|
|
})
|
2015-03-15 04:31:11 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-28 08:06:00 +00:00
|
|
|
currentContainerID, err := util.GetCurrentContainerID()
|
2015-03-15 04:31:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-11-28 08:06:00 +00:00
|
|
|
currentContainer, err := client.ContainerInspect(context.Background(), currentContainerID)
|
2015-03-15 04:31:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-05-24 00:21:28 +00:00
|
|
|
powerContainer, err := client.ContainerCreate(context.Background(),
|
|
|
|
&container.Config{
|
2015-03-15 04:31:11 +00:00
|
|
|
Image: currentContainer.Config.Image,
|
2015-03-19 22:36:40 +00:00
|
|
|
Cmd: cmd,
|
2015-03-15 04:31:11 +00:00
|
|
|
Env: []string{
|
|
|
|
"IN_DOCKER=true",
|
|
|
|
},
|
|
|
|
},
|
2016-05-24 00:21:28 +00:00
|
|
|
&container.HostConfig{
|
2015-03-15 04:31:11 +00:00
|
|
|
PidMode: "host",
|
|
|
|
VolumesFrom: []string{
|
|
|
|
currentContainer.ID,
|
|
|
|
},
|
|
|
|
Privileged: true,
|
2017-12-15 08:15:13 +00:00
|
|
|
}, nil, containerName)
|
2015-03-15 04:31:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-05-24 00:21:28 +00:00
|
|
|
err = client.ContainerStart(context.Background(), powerContainer.ID)
|
2015-03-15 04:31:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-07-28 03:27:40 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
2015-03-15 04:31:11 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Exit(0)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-05 04:14:22 +00:00
|
|
|
func reboot(name string, force bool, code uint) {
|
2015-02-21 20:22:59 +00:00
|
|
|
if os.Geteuid() != 0 {
|
2015-03-15 04:31:11 +00:00
|
|
|
log.Fatalf("%s: Need to be root", os.Args[0])
|
2015-02-21 20:22:59 +00:00
|
|
|
}
|
2015-03-15 04:31:11 +00:00
|
|
|
|
2017-07-28 03:27:40 +00:00
|
|
|
// 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(".")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2017-07-06 01:37:18 +00:00
|
|
|
// reboot -f should work even when system-docker is having problems
|
2017-07-05 04:14:22 +00:00
|
|
|
if !force {
|
2017-07-06 01:37:18 +00:00
|
|
|
if kexecFlag || previouskexecFlag || kexecAppendFlag != "" {
|
|
|
|
// pass through the cmdline args
|
|
|
|
name = ""
|
|
|
|
}
|
2017-07-05 04:14:22 +00:00
|
|
|
if err := runDocker(name); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2017-07-06 01:37:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2018-09-30 06:30:25 +00:00
|
|
|
Kexec(previouskexecFlag, filepath.Join(baseName, config.BootDir), kexecAppendFlag)
|
2017-07-06 01:37:18 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !force {
|
2017-07-05 04:14:22 +00:00
|
|
|
err := shutDownContainers()
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
}
|
2015-02-12 23:22:48 +00:00
|
|
|
}
|
2015-03-15 04:31:11 +00:00
|
|
|
|
|
|
|
syscall.Sync()
|
2017-07-05 04:14:22 +00:00
|
|
|
err := syscall.Reboot(int(code))
|
2015-02-12 23:22:48 +00:00
|
|
|
if err != nil {
|
2015-02-21 20:22:59 +00:00
|
|
|
log.Fatal(err)
|
2015-02-12 23:22:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func shutDownContainers() error {
|
|
|
|
var err error
|
|
|
|
shutDown := true
|
2015-03-15 04:31:11 +00:00
|
|
|
timeout := 2
|
|
|
|
for i, arg := range os.Args {
|
2015-02-12 23:22:48 +00:00
|
|
|
if arg == "-f" || arg == "--f" || arg == "--force" {
|
|
|
|
shutDown = false
|
|
|
|
}
|
|
|
|
if arg == "-t" || arg == "--t" || arg == "--timeout" {
|
|
|
|
if len(os.Args) > i+1 {
|
2015-03-15 04:31:11 +00:00
|
|
|
t, err := strconv.Atoi(os.Args[i+1])
|
|
|
|
if err != nil {
|
2015-02-12 23:22:48 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-03-15 04:31:11 +00:00
|
|
|
timeout = t
|
2015-02-12 23:22:48 +00:00
|
|
|
} else {
|
2015-03-15 04:31:11 +00:00
|
|
|
log.Error("please specify a timeout")
|
2015-02-12 23:22:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !shutDown {
|
|
|
|
return nil
|
|
|
|
}
|
2015-03-15 04:31:11 +00:00
|
|
|
client, err := docker.NewSystemClient()
|
2015-02-17 05:09:26 +00:00
|
|
|
|
2015-02-12 23:22:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-05-24 00:21:28 +00:00
|
|
|
filter := filters.NewArgs()
|
|
|
|
filter.Add("status", "running")
|
|
|
|
|
|
|
|
opts := types.ContainerListOptions{
|
|
|
|
All: true,
|
|
|
|
Filter: filter,
|
2015-03-15 04:31:11 +00:00
|
|
|
}
|
2015-02-12 23:22:48 +00:00
|
|
|
|
2016-05-24 00:21:28 +00:00
|
|
|
containers, err := client.ContainerList(context.Background(), opts)
|
2015-02-12 23:22:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-11-28 08:06:00 +00:00
|
|
|
currentContainerID, err := util.GetCurrentContainerID()
|
2015-02-12 23:22:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var stopErrorStrings []string
|
2017-07-28 01:50:07 +00:00
|
|
|
consoleContainerIdx := -1
|
2015-02-12 23:22:48 +00:00
|
|
|
|
2017-07-28 01:50:07 +00:00
|
|
|
for idx, container := range containers {
|
2016-11-28 08:06:00 +00:00
|
|
|
if container.ID == currentContainerID {
|
2015-02-12 23:22:48 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-07-28 01:50:07 +00:00
|
|
|
if container.Names[0] == "/console" {
|
|
|
|
consoleContainerIdx = idx
|
|
|
|
continue
|
|
|
|
}
|
2015-03-15 04:31:11 +00:00
|
|
|
|
2017-07-28 01:50:07 +00:00
|
|
|
log.Infof("Stopping %s : %s", container.Names[0], container.ID[:12])
|
2016-05-24 00:21:28 +00:00
|
|
|
stopErr := client.ContainerStop(context.Background(), container.ID, timeout)
|
2015-02-12 23:22:48 +00:00
|
|
|
if stopErr != nil {
|
2017-07-28 01:50:07 +00:00
|
|
|
log.Errorf("------- Error Stopping %s : %s", container.Names[0], stopErr.Error())
|
2015-03-15 04:31:11 +00:00
|
|
|
stopErrorStrings = append(stopErrorStrings, " ["+container.ID+"] "+stopErr.Error())
|
2015-02-12 23:22:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-28 03:27:40 +00:00
|
|
|
// lets see what containers are still running and only wait on those
|
|
|
|
containers, err = client.ContainerList(context.Background(), opts)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-02-12 23:22:48 +00:00
|
|
|
var waitErrorStrings []string
|
|
|
|
|
2017-07-28 03:27:40 +00:00
|
|
|
for idx, container := range containers {
|
2016-11-28 08:06:00 +00:00
|
|
|
if container.ID == currentContainerID {
|
2015-02-12 23:22:48 +00:00
|
|
|
continue
|
|
|
|
}
|
2017-07-28 01:50:07 +00:00
|
|
|
if container.Names[0] == "/console" {
|
2017-07-28 03:27:40 +00:00
|
|
|
consoleContainerIdx = idx
|
2017-07-28 01:50:07 +00:00
|
|
|
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])
|
2016-05-24 00:21:28 +00:00
|
|
|
_, waitErr := client.ContainerWait(context.Background(), container.ID)
|
2015-02-12 23:22:48 +00:00
|
|
|
if waitErr != nil {
|
2017-07-28 01:50:07 +00:00
|
|
|
log.Errorf("------- Error Waiting %v : %s", container.Names, waitErr.Error())
|
2015-03-15 04:31:11 +00:00
|
|
|
waitErrorStrings = append(waitErrorStrings, " ["+container.ID+"] "+waitErr.Error())
|
2015-02-12 23:22:48 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-17 05:09:26 +00:00
|
|
|
|
2015-02-12 23:22:48 +00:00
|
|
|
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
|
|
|
|
}
|