power.go 7.5 KB

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