mirror of
https://github.com/rancher/os.git
synced 2025-06-27 07:16:48 +00:00
313 lines
7.6 KiB
Go
313 lines
7.6 KiB
Go
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 != "" {
|
|
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",
|
|
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
|
|
}
|