cgroup_test.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. package worker
  2. import (
  3. "io/ioutil"
  4. "os"
  5. "os/exec"
  6. "path/filepath"
  7. "strconv"
  8. "strings"
  9. "testing"
  10. "time"
  11. "errors"
  12. "syscall"
  13. cgv1 "github.com/containerd/cgroups"
  14. cgv2 "github.com/containerd/cgroups/v2"
  15. units "github.com/docker/go-units"
  16. "github.com/moby/moby/pkg/reexec"
  17. . "github.com/smartystreets/goconvey/convey"
  18. )
  19. func init() {
  20. _, testReexec := os.LookupEnv("TESTREEXEC")
  21. if ! testReexec {
  22. reexec.Init()
  23. }
  24. }
  25. func TestReexec(t *testing.T) {
  26. testCase, testReexec := os.LookupEnv("TESTREEXEC")
  27. if ! testReexec {
  28. return
  29. }
  30. for len(os.Args) > 1 {
  31. thisArg := os.Args[1]
  32. os.Args = append([]string{os.Args[0]}, os.Args[2:]...)
  33. if thisArg == "--" {
  34. break
  35. }
  36. }
  37. switch testCase {
  38. case "1":
  39. Convey("Reexec should panic when command not found", t, func(ctx C){
  40. So(func(){
  41. reexec.Init()
  42. }, ShouldPanicWith, exec.ErrNotFound)
  43. })
  44. case "2":
  45. Convey("Reexec should run when fd 3 is not open", t, func(ctx C){
  46. So((func() error{
  47. pipe := os.NewFile(3, "pipe")
  48. if pipe == nil {
  49. return errors.New("pipe is nil")
  50. } else {
  51. _, err := pipe.Stat()
  52. return err
  53. }
  54. })(), ShouldNotBeNil)
  55. So(func(){
  56. reexec.Init()
  57. }, ShouldPanicWith, syscall.ENOEXEC)
  58. })
  59. case "3":
  60. Convey("Reexec should fail when fd 3 is sent with abrt cmd", t, func(ctx C){
  61. So(func(){
  62. reexec.Init()
  63. }, ShouldPanicWith, "Exited on request")
  64. })
  65. case "4":
  66. Convey("Reexec should run when fd 3 is sent with cont cmd", t, func(ctx C){
  67. So(func(){
  68. reexec.Init()
  69. }, ShouldPanicWith, syscall.ENOEXEC)
  70. })
  71. case "5":
  72. Convey("Reexec should not be triggered when argv[0] is not reexec", t, func(ctx C){
  73. So(func(){
  74. reexec.Init()
  75. }, ShouldNotPanic)
  76. })
  77. }
  78. }
  79. func TestCgroup(t *testing.T) {
  80. var cgcf *cgroupConfig
  81. Convey("init cgroup", t, func(ctx C){
  82. _, useCurrentCgroup := os.LookupEnv("USECURCGROUP")
  83. cgcf = &cgroupConfig{BasePath: "/sys/fs/cgroup", Group: "tunasync", Subsystem: "cpu"}
  84. if useCurrentCgroup {
  85. cgcf.Group = ""
  86. }
  87. err := initCgroup(cgcf)
  88. So(err, ShouldBeNil)
  89. if cgcf.isUnified {
  90. So(cgcf.cgMgrV2, ShouldNotBeNil)
  91. } else {
  92. So(cgcf.cgMgrV1, ShouldNotBeNil)
  93. }
  94. Convey("Cgroup Should Work", func(ctx C) {
  95. tmpDir, err := ioutil.TempDir("", "tunasync")
  96. defer os.RemoveAll(tmpDir)
  97. So(err, ShouldBeNil)
  98. cmdScript := filepath.Join(tmpDir, "cmd.sh")
  99. daemonScript := filepath.Join(tmpDir, "daemon.sh")
  100. tmpFile := filepath.Join(tmpDir, "log_file")
  101. bgPidfile := filepath.Join(tmpDir, "bg.pid")
  102. c := cmdConfig{
  103. name: "tuna-cgroup",
  104. upstreamURL: "http://mirrors.tuna.moe/",
  105. command: cmdScript + " " + daemonScript,
  106. workingDir: tmpDir,
  107. logDir: tmpDir,
  108. logFile: tmpFile,
  109. interval: 600 * time.Second,
  110. env: map[string]string{
  111. "BG_PIDFILE": bgPidfile,
  112. },
  113. }
  114. cmdScriptContent := `#!/bin/bash
  115. redirect-std() {
  116. [[ -t 0 ]] && exec </dev/null
  117. [[ -t 1 ]] && exec >/dev/null
  118. [[ -t 2 ]] && exec 2>/dev/null
  119. }
  120. # close all non-std* fds
  121. close-fds() {
  122. eval exec {3..255}\>\&-
  123. }
  124. # full daemonization of external command with setsid
  125. daemonize() {
  126. (
  127. redirect-std
  128. cd /
  129. close-fds
  130. exec setsid "$@"
  131. ) &
  132. }
  133. echo $$
  134. daemonize $@
  135. sleep 5
  136. `
  137. daemonScriptContent := `#!/bin/bash
  138. echo $$ > $BG_PIDFILE
  139. sleep 30
  140. `
  141. err = ioutil.WriteFile(cmdScript, []byte(cmdScriptContent), 0755)
  142. So(err, ShouldBeNil)
  143. err = ioutil.WriteFile(daemonScript, []byte(daemonScriptContent), 0755)
  144. So(err, ShouldBeNil)
  145. provider, err := newCmdProvider(c)
  146. So(err, ShouldBeNil)
  147. cg := newCgroupHook(provider, *cgcf, 0)
  148. provider.AddHook(cg)
  149. err = cg.preExec()
  150. So(err, ShouldBeNil)
  151. go func() {
  152. err := provider.Run(make(chan empty, 1))
  153. ctx.So(err, ShouldNotBeNil)
  154. }()
  155. time.Sleep(1 * time.Second)
  156. // Deamon should be started
  157. daemonPidBytes, err := ioutil.ReadFile(bgPidfile)
  158. So(err, ShouldBeNil)
  159. daemonPid := strings.Trim(string(daemonPidBytes), " \n")
  160. logger.Debug("daemon pid: %s", daemonPid)
  161. procDir := filepath.Join("/proc", daemonPid)
  162. _, err = os.Stat(procDir)
  163. So(err, ShouldBeNil)
  164. err = provider.Terminate()
  165. So(err, ShouldBeNil)
  166. // Deamon won't be killed
  167. _, err = os.Stat(procDir)
  168. So(err, ShouldBeNil)
  169. // Deamon can be killed by cgroup killer
  170. cg.postExec()
  171. _, err = os.Stat(procDir)
  172. So(os.IsNotExist(err), ShouldBeTrue)
  173. })
  174. Convey("Rsync Memory Should Be Limited", func() {
  175. tmpDir, err := ioutil.TempDir("", "tunasync")
  176. defer os.RemoveAll(tmpDir)
  177. So(err, ShouldBeNil)
  178. scriptFile := filepath.Join(tmpDir, "myrsync")
  179. tmpFile := filepath.Join(tmpDir, "log_file")
  180. c := rsyncConfig{
  181. name: "tuna-cgroup",
  182. upstreamURL: "rsync://rsync.tuna.moe/tuna/",
  183. rsyncCmd: scriptFile,
  184. workingDir: tmpDir,
  185. logDir: tmpDir,
  186. logFile: tmpFile,
  187. useIPv6: true,
  188. interval: 600 * time.Second,
  189. }
  190. provider, err := newRsyncProvider(c)
  191. So(err, ShouldBeNil)
  192. cg := newCgroupHook(provider, *cgcf, 512 * units.MiB)
  193. provider.AddHook(cg)
  194. err = cg.preExec()
  195. So(err, ShouldBeNil)
  196. if cgcf.isUnified {
  197. cgpath := filepath.Join(cgcf.BasePath, cgcf.Group, provider.Name())
  198. if useCurrentCgroup {
  199. group, err := cgv2.NestedGroupPath(filepath.Join("..", provider.Name()))
  200. So(err, ShouldBeNil)
  201. cgpath = filepath.Join(cgcf.BasePath, group)
  202. }
  203. memoLimit, err := ioutil.ReadFile(filepath.Join(cgpath, "memory.max"))
  204. So(err, ShouldBeNil)
  205. So(strings.Trim(string(memoLimit), "\n"), ShouldEqual, strconv.Itoa(512*1024*1024))
  206. } else {
  207. for _, subsys := range(cg.cgMgrV1.Subsystems()) {
  208. if subsys.Name() == cgv1.Memory {
  209. cgpath := filepath.Join(cgcf.Group, provider.Name())
  210. if useCurrentCgroup {
  211. p, err := cgv1.NestedPath(filepath.Join("..", provider.Name()))(cgv1.Memory)
  212. So(err, ShouldBeNil)
  213. cgpath = p
  214. }
  215. memoLimit, err := ioutil.ReadFile(filepath.Join(cgcf.BasePath, "memory", cgpath, "memory.limit_in_bytes"))
  216. So(err, ShouldBeNil)
  217. So(strings.Trim(string(memoLimit), "\n"), ShouldEqual, strconv.Itoa(512*1024*1024))
  218. }
  219. }
  220. }
  221. cg.postExec()
  222. So(cg.cgMgrV1, ShouldBeNil)
  223. })
  224. Reset(func() {
  225. if cgcf.isUnified {
  226. if cgcf.Group == "" {
  227. wkrg, err := cgv2.NestedGroupPath("");
  228. So(err, ShouldBeNil)
  229. wkrMgr, err := cgv2.LoadManager("/sys/fs/cgroup", wkrg);
  230. allCtrls, err := wkrMgr.Controllers()
  231. So(err, ShouldBeNil)
  232. err = wkrMgr.ToggleControllers(allCtrls, cgv2.Disable)
  233. So(err, ShouldBeNil)
  234. origMgr := cgcf.cgMgrV2
  235. for {
  236. logger.Debugf("Restoring pids")
  237. procs, err := wkrMgr.Procs(false)
  238. So(err, ShouldBeNil)
  239. if len(procs) == 0 {
  240. break
  241. }
  242. for _, p := range(procs) {
  243. if err := origMgr.AddProc(p); err != nil{
  244. if errors.Is(err, syscall.ESRCH) {
  245. logger.Debugf("Write pid %d to sub group failed: process vanished, ignoring")
  246. } else {
  247. So(err, ShouldBeNil)
  248. }
  249. }
  250. }
  251. }
  252. err = wkrMgr.Delete()
  253. So(err, ShouldBeNil)
  254. }
  255. } else {
  256. if cgcf.Group == "" {
  257. pather := (func(p cgv1.Path) (cgv1.Path){
  258. return func(subsys cgv1.Name) (string, error){
  259. path, err := p(subsys);
  260. if err != nil {
  261. return "", err
  262. }
  263. if path == "/" {
  264. return "", cgv1.ErrControllerNotActive
  265. }
  266. return path, err
  267. }
  268. })(cgv1.NestedPath(""))
  269. wkrMgr, err := cgv1.Load(cgv1.V1, pather, func(cfg *cgv1.InitConfig) error{
  270. cfg.InitCheck = cgv1.AllowAny
  271. return nil
  272. })
  273. So(err, ShouldBeNil)
  274. origMgr := cgcf.cgMgrV1
  275. for _, subsys := range(wkrMgr.Subsystems()){
  276. for {
  277. procs, err := wkrMgr.Processes(subsys.Name(), false)
  278. So(err, ShouldBeNil)
  279. if len(procs) == 0 {
  280. break
  281. }
  282. for _, proc := range(procs) {
  283. if err := origMgr.Add(proc); err != nil {
  284. if errors.Is(err, syscall.ESRCH) {
  285. logger.Debugf("Write pid %d to sub group failed: process vanished, ignoring")
  286. } else {
  287. So(err, ShouldBeNil)
  288. }
  289. }
  290. }
  291. }
  292. }
  293. err = wkrMgr.Delete()
  294. So(err, ShouldBeNil)
  295. }
  296. }
  297. })
  298. })
  299. }