power.go 7.4 KB

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