1
0
mirror of https://github.com/rancher/os.git synced 2025-06-27 15:26:50 +00:00
os/cmd/power/power.go

313 lines
7.6 KiB
Go
Raw Normal View History

package power
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
2015-02-17 05:09:26 +00:00
"syscall"
"time"
"github.com/rancher/os/cmd/control/install"
"github.com/rancher/os/config"
2018-09-16 04:55:26 +00:00
"github.com/rancher/os/pkg/docker"
"github.com/rancher/os/pkg/log"
2018-09-16 04:55:26 +00:00
"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
2015-03-19 22:36:40 +00:00
func runDocker(name string) error {
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
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
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,
})
if err != nil {
return err
}
}
2016-11-28 08:06:00 +00:00
currentContainerID, err := util.GetCurrentContainerID()
if err != nil {
return err
}
2016-11-28 08:06:00 +00:00
currentContainer, err := client.ContainerInspect(context.Background(), currentContainerID)
if err != nil {
return err
}
2016-05-24 00:21:28 +00:00
powerContainer, err := client.ContainerCreate(context.Background(),
&container.Config{
Image: currentContainer.Config.Image,
2015-03-19 22:36:40 +00:00
Cmd: cmd,
Env: []string{
"IN_DOCKER=true",
},
},
2016-05-24 00:21:28 +00:00
&container.HostConfig{
PidMode: "host",
VolumesFrom: []string{
currentContainer.ID,
},
Privileged: true,
}, nil, containerName)
if err != nil {
return err
}
2016-05-24 00:21:28 +00:00
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()
2015-02-17 05:09:26 +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,
}
2016-05-24 00:21:28 +00:00
containers, err := client.ContainerList(context.Background(), opts)
if err != nil {
return err
}
2016-11-28 08:06:00 +00:00
currentContainerID, err := util.GetCurrentContainerID()
if err != nil {
return err
}
var stopErrorStrings []string
consoleContainerIdx := -1
for idx, container := range containers {
2016-11-28 08:06:00 +00:00
if container.ID == currentContainerID {
continue
}
if container.Names[0] == "/console" {
consoleContainerIdx = idx
continue
}
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)
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 {
2016-11-28 08:06:00 +00:00
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])
2016-05-24 00:21:28 +00:00
_, 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())
}
}
2015-02-17 05:09:26 +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
}