daemon_unix_test.go 13 KB

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