daemon_unix_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. //go:build !windows
  2. // +build !windows
  3. package daemon // import "github.com/docker/docker/daemon"
  4. import (
  5. "errors"
  6. "os"
  7. "path/filepath"
  8. "testing"
  9. "github.com/docker/docker/api/types/blkiodev"
  10. containertypes "github.com/docker/docker/api/types/container"
  11. "github.com/docker/docker/container"
  12. "github.com/docker/docker/daemon/config"
  13. "github.com/docker/docker/pkg/sysinfo"
  14. "github.com/opencontainers/selinux/go-selinux"
  15. "golang.org/x/sys/unix"
  16. "gotest.tools/v3/assert"
  17. is "gotest.tools/v3/assert/cmp"
  18. )
  19. type fakeContainerGetter struct {
  20. containers map[string]*container.Container
  21. }
  22. func (f *fakeContainerGetter) GetContainer(cid string) (*container.Container, error) {
  23. ctr, ok := f.containers[cid]
  24. if !ok {
  25. return nil, errors.New("container not found")
  26. }
  27. return ctr, nil
  28. }
  29. // Unix test as uses settings which are not available on Windows
  30. func TestAdjustSharedNamespaceContainerName(t *testing.T) {
  31. fakeID := "abcdef1234567890"
  32. hostConfig := &containertypes.HostConfig{
  33. IpcMode: containertypes.IpcMode("container:base"),
  34. PidMode: containertypes.PidMode("container:base"),
  35. NetworkMode: containertypes.NetworkMode("container:base"),
  36. }
  37. containerStore := &fakeContainerGetter{}
  38. containerStore.containers = make(map[string]*container.Container)
  39. containerStore.containers["base"] = &container.Container{
  40. ID: fakeID,
  41. }
  42. adaptSharedNamespaceContainer(containerStore, hostConfig)
  43. if hostConfig.IpcMode != containertypes.IpcMode("container:"+fakeID) {
  44. t.Errorf("Expected IpcMode to be container:%s", fakeID)
  45. }
  46. if hostConfig.PidMode != containertypes.PidMode("container:"+fakeID) {
  47. t.Errorf("Expected PidMode to be container:%s", fakeID)
  48. }
  49. if hostConfig.NetworkMode != containertypes.NetworkMode("container:"+fakeID) {
  50. t.Errorf("Expected NetworkMode to be container:%s", fakeID)
  51. }
  52. }
  53. // Unix test as uses settings which are not available on Windows
  54. func TestAdjustCPUShares(t *testing.T) {
  55. tmp, err := os.MkdirTemp("", "docker-daemon-unix-test-")
  56. if err != nil {
  57. t.Fatal(err)
  58. }
  59. defer os.RemoveAll(tmp)
  60. daemon := &Daemon{
  61. repository: tmp,
  62. root: tmp,
  63. }
  64. muteLogs()
  65. hostConfig := &containertypes.HostConfig{
  66. Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
  67. }
  68. daemon.adaptContainerSettings(hostConfig, true)
  69. if hostConfig.CPUShares != linuxMinCPUShares {
  70. t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares)
  71. }
  72. hostConfig.CPUShares = linuxMaxCPUShares + 1
  73. daemon.adaptContainerSettings(hostConfig, true)
  74. if hostConfig.CPUShares != linuxMaxCPUShares {
  75. t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares)
  76. }
  77. hostConfig.CPUShares = 0
  78. daemon.adaptContainerSettings(hostConfig, true)
  79. if hostConfig.CPUShares != 0 {
  80. t.Error("Expected CPUShares to be unchanged")
  81. }
  82. hostConfig.CPUShares = 1024
  83. daemon.adaptContainerSettings(hostConfig, true)
  84. if hostConfig.CPUShares != 1024 {
  85. t.Error("Expected CPUShares to be unchanged")
  86. }
  87. }
  88. // Unix test as uses settings which are not available on Windows
  89. func TestAdjustCPUSharesNoAdjustment(t *testing.T) {
  90. tmp, err := os.MkdirTemp("", "docker-daemon-unix-test-")
  91. if err != nil {
  92. t.Fatal(err)
  93. }
  94. defer os.RemoveAll(tmp)
  95. daemon := &Daemon{
  96. repository: tmp,
  97. root: tmp,
  98. }
  99. hostConfig := &containertypes.HostConfig{
  100. Resources: containertypes.Resources{CPUShares: linuxMinCPUShares - 1},
  101. }
  102. daemon.adaptContainerSettings(hostConfig, false)
  103. if hostConfig.CPUShares != linuxMinCPUShares-1 {
  104. t.Errorf("Expected CPUShares to be %d", linuxMinCPUShares-1)
  105. }
  106. hostConfig.CPUShares = linuxMaxCPUShares + 1
  107. daemon.adaptContainerSettings(hostConfig, false)
  108. if hostConfig.CPUShares != linuxMaxCPUShares+1 {
  109. t.Errorf("Expected CPUShares to be %d", linuxMaxCPUShares+1)
  110. }
  111. hostConfig.CPUShares = 0
  112. daemon.adaptContainerSettings(hostConfig, false)
  113. if hostConfig.CPUShares != 0 {
  114. t.Error("Expected CPUShares to be unchanged")
  115. }
  116. hostConfig.CPUShares = 1024
  117. daemon.adaptContainerSettings(hostConfig, false)
  118. if hostConfig.CPUShares != 1024 {
  119. t.Error("Expected CPUShares to be unchanged")
  120. }
  121. }
  122. // Unix test as uses settings which are not available on Windows
  123. func TestParseSecurityOptWithDeprecatedColon(t *testing.T) {
  124. opts := &container.SecurityOptions{}
  125. cfg := &containertypes.HostConfig{}
  126. // test apparmor
  127. cfg.SecurityOpt = []string{"apparmor=test_profile"}
  128. if err := parseSecurityOpt(opts, cfg); err != nil {
  129. t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
  130. }
  131. if opts.AppArmorProfile != "test_profile" {
  132. t.Fatalf("Unexpected AppArmorProfile, expected: \"test_profile\", got %q", opts.AppArmorProfile)
  133. }
  134. // test seccomp
  135. sp := "/path/to/seccomp_test.json"
  136. cfg.SecurityOpt = []string{"seccomp=" + sp}
  137. if err := parseSecurityOpt(opts, cfg); err != nil {
  138. t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
  139. }
  140. if opts.SeccompProfile != sp {
  141. t.Fatalf("Unexpected AppArmorProfile, expected: %q, got %q", sp, opts.SeccompProfile)
  142. }
  143. // test valid label
  144. cfg.SecurityOpt = []string{"label=user:USER"}
  145. if err := parseSecurityOpt(opts, cfg); err != nil {
  146. t.Fatalf("Unexpected parseSecurityOpt error: %v", err)
  147. }
  148. // test invalid label
  149. cfg.SecurityOpt = []string{"label"}
  150. if err := parseSecurityOpt(opts, cfg); err == nil {
  151. t.Fatal("Expected parseSecurityOpt error, got nil")
  152. }
  153. // test invalid opt
  154. cfg.SecurityOpt = []string{"test"}
  155. if err := parseSecurityOpt(opts, cfg); err == nil {
  156. t.Fatal("Expected parseSecurityOpt error, got nil")
  157. }
  158. }
  159. func TestParseSecurityOpt(t *testing.T) {
  160. t.Run("apparmor", func(t *testing.T) {
  161. secOpts := &container.SecurityOptions{}
  162. err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
  163. SecurityOpt: []string{"apparmor=test_profile"},
  164. })
  165. assert.Check(t, err)
  166. assert.Equal(t, secOpts.AppArmorProfile, "test_profile")
  167. })
  168. t.Run("apparmor using legacy separator", func(t *testing.T) {
  169. secOpts := &container.SecurityOptions{}
  170. err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
  171. SecurityOpt: []string{"apparmor:test_profile"},
  172. })
  173. assert.Check(t, err)
  174. assert.Equal(t, secOpts.AppArmorProfile, "test_profile")
  175. })
  176. t.Run("seccomp", func(t *testing.T) {
  177. secOpts := &container.SecurityOptions{}
  178. err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
  179. SecurityOpt: []string{"seccomp=/path/to/seccomp_test.json"},
  180. })
  181. assert.Check(t, err)
  182. assert.Equal(t, secOpts.SeccompProfile, "/path/to/seccomp_test.json")
  183. })
  184. t.Run("valid label", func(t *testing.T) {
  185. secOpts := &container.SecurityOptions{}
  186. err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
  187. SecurityOpt: []string{"label=user:USER"},
  188. })
  189. assert.Check(t, err)
  190. if selinux.GetEnabled() {
  191. // TODO(thaJeztah): set expected labels here (or "partial" if depends on host)
  192. // assert.Check(t, is.Equal(secOpts.MountLabel, ""))
  193. // assert.Check(t, is.Equal(secOpts.ProcessLabel, ""))
  194. } else {
  195. assert.Check(t, is.Equal(secOpts.MountLabel, ""))
  196. assert.Check(t, is.Equal(secOpts.ProcessLabel, ""))
  197. }
  198. })
  199. t.Run("invalid label", func(t *testing.T) {
  200. secOpts := &container.SecurityOptions{}
  201. err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
  202. SecurityOpt: []string{"label"},
  203. })
  204. assert.Error(t, err, `invalid --security-opt 1: "label"`)
  205. })
  206. t.Run("invalid option (no value)", func(t *testing.T) {
  207. secOpts := &container.SecurityOptions{}
  208. err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
  209. SecurityOpt: []string{"unknown"},
  210. })
  211. assert.Error(t, err, `invalid --security-opt 1: "unknown"`)
  212. })
  213. t.Run("unknown option", func(t *testing.T) {
  214. secOpts := &container.SecurityOptions{}
  215. err := parseSecurityOpt(secOpts, &containertypes.HostConfig{
  216. SecurityOpt: []string{"unknown=something"},
  217. })
  218. assert.Error(t, err, `invalid --security-opt 2: "unknown=something"`)
  219. })
  220. }
  221. func TestParseNNPSecurityOptions(t *testing.T) {
  222. daemon := &Daemon{
  223. configStore: &config.Config{NoNewPrivileges: true},
  224. }
  225. opts := &container.SecurityOptions{}
  226. cfg := &containertypes.HostConfig{}
  227. // test NNP when "daemon:true" and "no-new-privileges=false""
  228. cfg.SecurityOpt = []string{"no-new-privileges=false"}
  229. if err := daemon.parseSecurityOpt(opts, cfg); err != nil {
  230. t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err)
  231. }
  232. if opts.NoNewPrivileges {
  233. t.Fatalf("container.NoNewPrivileges should be FALSE: %v", opts.NoNewPrivileges)
  234. }
  235. // test NNP when "daemon:false" and "no-new-privileges=true""
  236. daemon.configStore.NoNewPrivileges = false
  237. cfg.SecurityOpt = []string{"no-new-privileges=true"}
  238. if err := daemon.parseSecurityOpt(opts, cfg); err != nil {
  239. t.Fatalf("Unexpected daemon.parseSecurityOpt error: %v", err)
  240. }
  241. if !opts.NoNewPrivileges {
  242. t.Fatalf("container.NoNewPrivileges should be TRUE: %v", opts.NoNewPrivileges)
  243. }
  244. }
  245. func TestVerifyPlatformContainerResources(t *testing.T) {
  246. t.Parallel()
  247. var (
  248. no = false
  249. yes = true
  250. )
  251. withMemoryLimit := func(si *sysinfo.SysInfo) {
  252. si.MemoryLimit = true
  253. }
  254. withSwapLimit := func(si *sysinfo.SysInfo) {
  255. si.SwapLimit = true
  256. }
  257. withOomKillDisable := func(si *sysinfo.SysInfo) {
  258. si.OomKillDisable = true
  259. }
  260. tests := []struct {
  261. name string
  262. resources containertypes.Resources
  263. sysInfo sysinfo.SysInfo
  264. update bool
  265. expectedWarnings []string
  266. }{
  267. {
  268. name: "no-oom-kill-disable",
  269. resources: containertypes.Resources{},
  270. sysInfo: sysInfo(t, withMemoryLimit),
  271. expectedWarnings: []string{},
  272. },
  273. {
  274. name: "oom-kill-disable-disabled",
  275. resources: containertypes.Resources{
  276. OomKillDisable: &no,
  277. },
  278. sysInfo: sysInfo(t, withMemoryLimit),
  279. expectedWarnings: []string{},
  280. },
  281. {
  282. name: "oom-kill-disable-not-supported",
  283. resources: containertypes.Resources{
  284. OomKillDisable: &yes,
  285. },
  286. sysInfo: sysInfo(t, withMemoryLimit),
  287. expectedWarnings: []string{
  288. "Your kernel does not support OomKillDisable. OomKillDisable discarded.",
  289. },
  290. },
  291. {
  292. name: "oom-kill-disable-without-memory-constraints",
  293. resources: containertypes.Resources{
  294. OomKillDisable: &yes,
  295. Memory: 0,
  296. },
  297. sysInfo: sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
  298. expectedWarnings: []string{
  299. "OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
  300. },
  301. },
  302. {
  303. name: "oom-kill-disable-with-memory-constraints-but-no-memory-limit-support",
  304. resources: containertypes.Resources{
  305. OomKillDisable: &yes,
  306. Memory: linuxMinMemory,
  307. },
  308. sysInfo: sysInfo(t, withOomKillDisable),
  309. expectedWarnings: []string{
  310. "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.",
  311. "OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.",
  312. },
  313. },
  314. {
  315. name: "oom-kill-disable-with-memory-constraints",
  316. resources: containertypes.Resources{
  317. OomKillDisable: &yes,
  318. Memory: linuxMinMemory,
  319. },
  320. sysInfo: sysInfo(t, withMemoryLimit, withOomKillDisable, withSwapLimit),
  321. expectedWarnings: []string{},
  322. },
  323. }
  324. for _, tc := range tests {
  325. tc := tc
  326. t.Run(tc.name, func(t *testing.T) {
  327. t.Parallel()
  328. warnings, err := verifyPlatformContainerResources(&tc.resources, &tc.sysInfo, tc.update)
  329. assert.NilError(t, err)
  330. for _, w := range tc.expectedWarnings {
  331. assert.Assert(t, is.Contains(warnings, w))
  332. }
  333. })
  334. }
  335. }
  336. func sysInfo(t *testing.T, opts ...func(*sysinfo.SysInfo)) sysinfo.SysInfo {
  337. t.Helper()
  338. si := sysinfo.SysInfo{}
  339. for _, opt := range opts {
  340. opt(&si)
  341. }
  342. if si.OomKillDisable {
  343. t.Log(t.Name(), "OOM disable supported")
  344. }
  345. return si
  346. }
  347. const (
  348. // prepare major 0x1FD(509 in decimal) and minor 0x130(304)
  349. DEVNO = 0x11FD30
  350. MAJOR = 509
  351. MINOR = 304
  352. WEIGHT = 1024
  353. )
  354. func deviceTypeMock(t *testing.T, testAndCheck func(string)) {
  355. if os.Getuid() != 0 {
  356. t.Skip("root required") // for mknod
  357. }
  358. t.Parallel()
  359. tempDir, err := os.MkdirTemp("", "tempDevDir"+t.Name())
  360. assert.NilError(t, err, "create temp file")
  361. tempFile := filepath.Join(tempDir, "dev")
  362. defer os.RemoveAll(tempDir)
  363. if err = unix.Mknod(tempFile, unix.S_IFCHR, DEVNO); err != nil {
  364. t.Fatalf("mknod error %s(%x): %v", tempFile, DEVNO, err)
  365. }
  366. testAndCheck(tempFile)
  367. }
  368. func TestGetBlkioWeightDevices(t *testing.T) {
  369. deviceTypeMock(t, func(tempFile string) {
  370. mockResource := containertypes.Resources{
  371. BlkioWeightDevice: []*blkiodev.WeightDevice{{Path: tempFile, Weight: WEIGHT}},
  372. }
  373. weightDevs, err := getBlkioWeightDevices(mockResource)
  374. assert.NilError(t, err, "getBlkioWeightDevices")
  375. assert.Check(t, is.Len(weightDevs, 1), "getBlkioWeightDevices")
  376. assert.Check(t, weightDevs[0].Major == MAJOR, "get major device type")
  377. assert.Check(t, weightDevs[0].Minor == MINOR, "get minor device type")
  378. assert.Check(t, *weightDevs[0].Weight == WEIGHT, "get device weight")
  379. })
  380. }
  381. func TestGetBlkioThrottleDevices(t *testing.T) {
  382. deviceTypeMock(t, func(tempFile string) {
  383. mockDevs := []*blkiodev.ThrottleDevice{{Path: tempFile, Rate: WEIGHT}}
  384. retDevs, err := getBlkioThrottleDevices(mockDevs)
  385. assert.NilError(t, err, "getBlkioThrottleDevices")
  386. assert.Check(t, is.Len(retDevs, 1), "getBlkioThrottleDevices")
  387. assert.Check(t, retDevs[0].Major == MAJOR, "get major device type")
  388. assert.Check(t, retDevs[0].Minor == MINOR, "get minor device type")
  389. assert.Check(t, retDevs[0].Rate == WEIGHT, "get device rate")
  390. })
  391. }