power.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package power
  2. import (
  3. "errors"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strconv"
  8. "strings"
  9. "syscall"
  10. "time"
  11. "github.com/rancher/os/cmd/control/install"
  12. "github.com/rancher/os/config"
  13. "github.com/rancher/os/pkg/docker"
  14. "github.com/rancher/os/pkg/log"
  15. "github.com/rancher/os/pkg/util"
  16. "github.com/docker/engine-api/types"
  17. "github.com/docker/engine-api/types/container"
  18. "github.com/docker/engine-api/types/filters"
  19. "golang.org/x/net/context"
  20. )
  21. // You can't shutdown the system from a process in console because we want to stop the console container.
  22. // If you do that you kill yourself. So we spawn a separate container to do power operations
  23. // This can up because on shutdown we want ssh to gracefully die, terminating ssh connections and not just hanging tcp session
  24. //
  25. // Be careful of container name. only [a-zA-Z0-9][a-zA-Z0-9_.-] are allowed
  26. func runDocker(name string) error {
  27. if os.ExpandEnv("${IN_DOCKER}") == "true" {
  28. return nil
  29. }
  30. client, err := docker.NewSystemClient()
  31. if err != nil {
  32. return err
  33. }
  34. cmd := os.Args
  35. log.Debugf("runDocker cmd: %s", cmd)
  36. if name == "" {
  37. name = filepath.Base(os.Args[0])
  38. }
  39. containerName := strings.TrimPrefix(strings.Join(strings.Split(name, "/"), "-"), "-")
  40. existing, err := client.ContainerInspect(context.Background(), containerName)
  41. if err == nil && existing.ID != "" {
  42. // remove the old version of reboot
  43. err := client.ContainerRemove(context.Background(), types.ContainerRemoveOptions{
  44. ContainerID: existing.ID,
  45. })
  46. if err != nil {
  47. return err
  48. }
  49. }
  50. currentContainerID, err := util.GetCurrentContainerID()
  51. if err != nil {
  52. return err
  53. }
  54. currentContainer, err := client.ContainerInspect(context.Background(), currentContainerID)
  55. if err != nil {
  56. return err
  57. }
  58. powerContainer, err := client.ContainerCreate(context.Background(),
  59. &container.Config{
  60. Image: currentContainer.Config.Image,
  61. Cmd: cmd,
  62. Env: []string{
  63. "IN_DOCKER=true",
  64. },
  65. },
  66. &container.HostConfig{
  67. PidMode: "host",
  68. NetworkMode: "none",
  69. VolumesFrom: []string{
  70. currentContainer.ID,
  71. },
  72. Privileged: true,
  73. }, nil, containerName)
  74. if err != nil {
  75. return err
  76. }
  77. err = client.ContainerStart(context.Background(), powerContainer.ID)
  78. if err != nil {
  79. return err
  80. }
  81. reader, err := client.ContainerLogs(context.Background(), types.ContainerLogsOptions{
  82. ContainerID: powerContainer.ID,
  83. ShowStderr: true,
  84. ShowStdout: true,
  85. Follow: true,
  86. })
  87. if err != nil {
  88. log.Fatal(err)
  89. }
  90. for {
  91. p := make([]byte, 4096)
  92. n, err := reader.Read(p)
  93. if err != nil {
  94. log.Error(err)
  95. if n == 0 {
  96. reader.Close()
  97. break
  98. }
  99. }
  100. if n > 0 {
  101. fmt.Print(string(p))
  102. }
  103. }
  104. if err != nil {
  105. log.Fatal(err)
  106. }
  107. os.Exit(0)
  108. return nil
  109. }
  110. func reboot(name string, force bool, code uint) {
  111. if os.Geteuid() != 0 {
  112. log.Fatalf("%s: Need to be root", os.Args[0])
  113. }
  114. // Add shutdown timeout
  115. cfg := config.LoadConfig()
  116. timeoutValue := cfg.Rancher.ShutdownTimeout
  117. if timeoutValue == 0 {
  118. timeoutValue = 60
  119. }
  120. if timeoutValue < 5 {
  121. timeoutValue = 5
  122. }
  123. log.Infof("Setting %s timeout to %d (rancher.shutdown_timeout set to %d)", os.Args[0], timeoutValue, cfg.Rancher.ShutdownTimeout)
  124. go func() {
  125. timeout := time.After(time.Duration(timeoutValue) * time.Second)
  126. tick := time.Tick(100 * time.Millisecond)
  127. // Keep trying until we're timed out or got a result or got an error
  128. for {
  129. select {
  130. // Got a timeout! fail with a timeout error
  131. case <-timeout:
  132. log.Errorf("Container shutdown taking too long, forcing %s.", os.Args[0])
  133. syscall.Sync()
  134. syscall.Reboot(int(code))
  135. case <-tick:
  136. fmt.Printf(".")
  137. }
  138. }
  139. }()
  140. // reboot -f should work even when system-docker is having problems
  141. if !force {
  142. if kexecFlag || previouskexecFlag || kexecAppendFlag != "" {
  143. // pass through the cmdline args
  144. name = ""
  145. }
  146. if err := runDocker(name); err != nil {
  147. log.Fatal(err)
  148. }
  149. }
  150. if kexecFlag || previouskexecFlag || kexecAppendFlag != "" {
  151. // need to mount boot dir, or `system-docker run -v /:/host -w /host/boot` ?
  152. baseName := "/mnt/new_img"
  153. _, _, err := install.MountDevice(baseName, "", "", false)
  154. if err != nil {
  155. log.Errorf("ERROR: can't Kexec: %s", err)
  156. return
  157. }
  158. defer util.Unmount(baseName)
  159. Kexec(previouskexecFlag, filepath.Join(baseName, config.BootDir), kexecAppendFlag)
  160. return
  161. }
  162. if !force {
  163. err := shutDownContainers()
  164. if err != nil {
  165. log.Error(err)
  166. }
  167. }
  168. syscall.Sync()
  169. err := syscall.Reboot(int(code))
  170. if err != nil {
  171. log.Fatal(err)
  172. }
  173. }
  174. func shutDownContainers() error {
  175. var err error
  176. shutDown := true
  177. timeout := 2
  178. for i, arg := range os.Args {
  179. if arg == "-f" || arg == "--f" || arg == "--force" {
  180. shutDown = false
  181. }
  182. if arg == "-t" || arg == "--t" || arg == "--timeout" {
  183. if len(os.Args) > i+1 {
  184. t, err := strconv.Atoi(os.Args[i+1])
  185. if err != nil {
  186. return err
  187. }
  188. timeout = t
  189. } else {
  190. log.Error("please specify a timeout")
  191. }
  192. }
  193. }
  194. if !shutDown {
  195. return nil
  196. }
  197. client, err := docker.NewSystemClient()
  198. if err != nil {
  199. return err
  200. }
  201. filter := filters.NewArgs()
  202. filter.Add("status", "running")
  203. opts := types.ContainerListOptions{
  204. All: true,
  205. Filter: filter,
  206. }
  207. containers, err := client.ContainerList(context.Background(), opts)
  208. if err != nil {
  209. return err
  210. }
  211. currentContainerID, err := util.GetCurrentContainerID()
  212. if err != nil {
  213. return err
  214. }
  215. var stopErrorStrings []string
  216. consoleContainerIdx := -1
  217. for idx, container := range containers {
  218. if container.ID == currentContainerID {
  219. continue
  220. }
  221. if container.Names[0] == "/console" {
  222. consoleContainerIdx = idx
  223. continue
  224. }
  225. log.Infof("Stopping %s : %s", container.Names[0], container.ID[:12])
  226. stopErr := client.ContainerStop(context.Background(), container.ID, timeout)
  227. if stopErr != nil {
  228. log.Errorf("------- Error Stopping %s : %s", container.Names[0], stopErr.Error())
  229. stopErrorStrings = append(stopErrorStrings, " ["+container.ID+"] "+stopErr.Error())
  230. }
  231. }
  232. // lets see what containers are still running and only wait on those
  233. containers, err = client.ContainerList(context.Background(), opts)
  234. if err != nil {
  235. return err
  236. }
  237. var waitErrorStrings []string
  238. for idx, container := range containers {
  239. if container.ID == currentContainerID {
  240. continue
  241. }
  242. if container.Names[0] == "/console" {
  243. consoleContainerIdx = idx
  244. continue
  245. }
  246. log.Infof("Waiting %s : %s", container.Names[0], container.ID[:12])
  247. _, waitErr := client.ContainerWait(context.Background(), container.ID)
  248. if waitErr != nil {
  249. log.Errorf("------- Error Waiting %s : %s", container.Names[0], waitErr.Error())
  250. waitErrorStrings = append(waitErrorStrings, " ["+container.ID+"] "+waitErr.Error())
  251. }
  252. }
  253. // and now stop the console
  254. if consoleContainerIdx != -1 {
  255. container := containers[consoleContainerIdx]
  256. log.Infof("Console Stopping %v : %s", container.Names, container.ID[:12])
  257. stopErr := client.ContainerStop(context.Background(), container.ID, timeout)
  258. if stopErr != nil {
  259. log.Errorf("------- Error Stopping %v : %s", container.Names, stopErr.Error())
  260. stopErrorStrings = append(stopErrorStrings, " ["+container.ID+"] "+stopErr.Error())
  261. }
  262. log.Infof("Console Waiting %v : %s", container.Names, container.ID[:12])
  263. _, waitErr := client.ContainerWait(context.Background(), container.ID)
  264. if waitErr != nil {
  265. log.Errorf("------- Error Waiting %v : %s", container.Names, waitErr.Error())
  266. waitErrorStrings = append(waitErrorStrings, " ["+container.ID+"] "+waitErr.Error())
  267. }
  268. }
  269. if len(waitErrorStrings) != 0 || len(stopErrorStrings) != 0 {
  270. return errors.New("error while stopping \n1. STOP Errors [" + strings.Join(stopErrorStrings, ",") + "] \n2. WAIT Errors [" + strings.Join(waitErrorStrings, ",") + "]")
  271. }
  272. return nil
  273. }