state_file_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package state
  14. import (
  15. "bytes"
  16. "fmt"
  17. "io"
  18. "io/ioutil"
  19. "os"
  20. "path"
  21. "reflect"
  22. "strings"
  23. "testing"
  24. "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/containermap"
  25. "k8s.io/kubernetes/pkg/kubelet/cm/cpuset"
  26. )
  27. func writeToStateFile(statefile string, content string) {
  28. ioutil.WriteFile(statefile, []byte(content), 0644)
  29. }
  30. // AssertStateEqual marks provided test as failed if provided states differ
  31. func AssertStateEqual(t *testing.T, sf State, sm State) {
  32. cpusetSf := sf.GetDefaultCPUSet()
  33. cpusetSm := sm.GetDefaultCPUSet()
  34. if !cpusetSf.Equals(cpusetSm) {
  35. t.Errorf("State CPUSet mismatch. Have %v, want %v", cpusetSf, cpusetSm)
  36. }
  37. cpuassignmentSf := sf.GetCPUAssignments()
  38. cpuassignmentSm := sm.GetCPUAssignments()
  39. if !reflect.DeepEqual(cpuassignmentSf, cpuassignmentSm) {
  40. t.Errorf("State CPU assignments mismatch. Have %s, want %s", cpuassignmentSf, cpuassignmentSm)
  41. }
  42. }
  43. func stderrCapture(t *testing.T, f func() State) (bytes.Buffer, State) {
  44. stderr := os.Stderr
  45. readBuffer, writeBuffer, err := os.Pipe()
  46. if err != nil {
  47. t.Errorf("cannot create pipe: %v", err.Error())
  48. }
  49. os.Stderr = writeBuffer
  50. var outputBuffer bytes.Buffer
  51. state := f()
  52. writeBuffer.Close()
  53. io.Copy(&outputBuffer, readBuffer)
  54. os.Stderr = stderr
  55. return outputBuffer, state
  56. }
  57. func TestFileStateTryRestore(t *testing.T) {
  58. testCases := []struct {
  59. description string
  60. stateFileContent string
  61. policyName string
  62. initialContainers containermap.ContainerMap
  63. expErr string
  64. expectedState *stateMemory
  65. }{
  66. {
  67. "Invalid JSON - one byte file",
  68. "\n",
  69. "none",
  70. containermap.ContainerMap{},
  71. "[cpumanager] state file: unable to restore state from disk (unexpected end of JSON input)",
  72. &stateMemory{},
  73. },
  74. {
  75. "Invalid JSON - invalid content",
  76. "{",
  77. "none",
  78. containermap.ContainerMap{},
  79. "[cpumanager] state file: unable to restore state from disk (unexpected end of JSON input)",
  80. &stateMemory{},
  81. },
  82. {
  83. "Try restore defaultCPUSet only",
  84. `{"policyName": "none", "defaultCpuSet": "4-6"}`,
  85. "none",
  86. containermap.ContainerMap{},
  87. "",
  88. &stateMemory{
  89. assignments: ContainerCPUAssignments{},
  90. defaultCPUSet: cpuset.NewCPUSet(4, 5, 6),
  91. },
  92. },
  93. {
  94. "Try restore defaultCPUSet only - invalid name",
  95. `{"policyName": "none", "defaultCpuSet" "4-6"}`,
  96. "none",
  97. containermap.ContainerMap{},
  98. `[cpumanager] state file: unable to restore state from disk (invalid character '"' after object key)`,
  99. &stateMemory{},
  100. },
  101. {
  102. "Try restore assignments only",
  103. `{
  104. "policyName": "none",
  105. "entries": {
  106. "pod": {
  107. "container1": "4-6",
  108. "container2": "1-3"
  109. }
  110. }
  111. }`,
  112. "none",
  113. containermap.ContainerMap{},
  114. "",
  115. &stateMemory{
  116. assignments: ContainerCPUAssignments{
  117. "pod": map[string]cpuset.CPUSet{
  118. "container1": cpuset.NewCPUSet(4, 5, 6),
  119. "container2": cpuset.NewCPUSet(1, 2, 3),
  120. },
  121. },
  122. defaultCPUSet: cpuset.NewCPUSet(),
  123. },
  124. },
  125. {
  126. "Try restore invalid policy name",
  127. `{
  128. "policyName": "A",
  129. "defaultCpuSet": "0-7",
  130. "entries": {}
  131. }`,
  132. "B",
  133. containermap.ContainerMap{},
  134. `[cpumanager] state file: unable to restore state from disk (policy configured "B" != policy from state file "A")`,
  135. &stateMemory{},
  136. },
  137. {
  138. "Try restore invalid assignments",
  139. `{"entries": }`,
  140. "none",
  141. containermap.ContainerMap{},
  142. "[cpumanager] state file: unable to restore state from disk (invalid character '}' looking for beginning of value)",
  143. &stateMemory{},
  144. },
  145. {
  146. "Try restore valid file",
  147. `{
  148. "policyName": "none",
  149. "defaultCpuSet": "23-24",
  150. "entries": {
  151. "pod": {
  152. "container1": "4-6",
  153. "container2": "1-3"
  154. }
  155. }
  156. }`,
  157. "none",
  158. containermap.ContainerMap{},
  159. "",
  160. &stateMemory{
  161. assignments: ContainerCPUAssignments{
  162. "pod": map[string]cpuset.CPUSet{
  163. "container1": cpuset.NewCPUSet(4, 5, 6),
  164. "container2": cpuset.NewCPUSet(1, 2, 3),
  165. },
  166. },
  167. defaultCPUSet: cpuset.NewCPUSet(23, 24),
  168. },
  169. },
  170. {
  171. "Try restore un-parsable defaultCPUSet ",
  172. `{
  173. "policyName": "none",
  174. "defaultCpuSet": "2-sd"
  175. }`,
  176. "none",
  177. containermap.ContainerMap{},
  178. `[cpumanager] state file: unable to restore state from disk (strconv.Atoi: parsing "sd": invalid syntax)`,
  179. &stateMemory{},
  180. },
  181. {
  182. "Try restore un-parsable assignments",
  183. `{
  184. "policyName": "none",
  185. "defaultCpuSet": "23-24",
  186. "entries": {
  187. "pod": {
  188. "container1": "p-6",
  189. "container2": "1-3"
  190. }
  191. }
  192. }`,
  193. "none",
  194. containermap.ContainerMap{},
  195. `[cpumanager] state file: unable to restore state from disk (strconv.Atoi: parsing "p": invalid syntax)`,
  196. &stateMemory{},
  197. },
  198. {
  199. "tryRestoreState creates empty state file",
  200. "",
  201. "none",
  202. containermap.ContainerMap{},
  203. "",
  204. &stateMemory{
  205. assignments: ContainerCPUAssignments{},
  206. defaultCPUSet: cpuset.NewCPUSet(),
  207. },
  208. },
  209. {
  210. "Try restore with migration",
  211. `{
  212. "policyName": "none",
  213. "defaultCpuSet": "23-24",
  214. "entries": {
  215. "containerID1": "4-6",
  216. "containerID2": "1-3"
  217. }
  218. }`,
  219. "none",
  220. func() containermap.ContainerMap {
  221. cm := containermap.NewContainerMap()
  222. cm.Add("pod", "container1", "containerID1")
  223. cm.Add("pod", "container2", "containerID2")
  224. return cm
  225. }(),
  226. "",
  227. &stateMemory{
  228. assignments: ContainerCPUAssignments{
  229. "pod": map[string]cpuset.CPUSet{
  230. "container1": cpuset.NewCPUSet(4, 5, 6),
  231. "container2": cpuset.NewCPUSet(1, 2, 3),
  232. },
  233. },
  234. defaultCPUSet: cpuset.NewCPUSet(23, 24),
  235. },
  236. },
  237. }
  238. for idx, tc := range testCases {
  239. t.Run(tc.description, func(t *testing.T) {
  240. sfilePath, err := ioutil.TempFile("/tmp", fmt.Sprintf("cpumanager_state_file_test_%d", idx))
  241. if err != nil {
  242. t.Errorf("cannot create temporary file: %q", err.Error())
  243. }
  244. // Don't create state file, let tryRestoreState figure out that is should create
  245. if tc.stateFileContent != "" {
  246. writeToStateFile(sfilePath.Name(), tc.stateFileContent)
  247. }
  248. // Always remove file - regardless of who created
  249. defer os.Remove(sfilePath.Name())
  250. logData, fileState := stderrCapture(t, func() State {
  251. newFileState, _ := NewFileState(sfilePath.Name(), tc.policyName, tc.initialContainers)
  252. return newFileState
  253. })
  254. if tc.expErr != "" {
  255. if logData.String() != "" {
  256. if !strings.Contains(logData.String(), tc.expErr) {
  257. t.Errorf("tryRestoreState() error = %v, wantErr %v", logData.String(), tc.expErr)
  258. return
  259. }
  260. } else {
  261. t.Errorf("tryRestoreState() error = nil, wantErr %v", tc.expErr)
  262. return
  263. }
  264. }
  265. if fileState == nil {
  266. return
  267. }
  268. AssertStateEqual(t, fileState, tc.expectedState)
  269. })
  270. }
  271. }
  272. func TestFileStateTryRestoreError(t *testing.T) {
  273. testCases := []struct {
  274. description string
  275. expErr error
  276. }{
  277. {
  278. " create file error",
  279. fmt.Errorf("[cpumanager] state file not written"),
  280. },
  281. }
  282. for _, testCase := range testCases {
  283. t.Run(testCase.description, func(t *testing.T) {
  284. sfilePath := path.Join("/invalid_path/to_some_dir", "cpumanager_state_file_test")
  285. _, err := NewFileState(sfilePath, "static", nil)
  286. if !reflect.DeepEqual(err, testCase.expErr) {
  287. t.Errorf("unexpected error, expected: %s, got: %s", testCase.expErr, err)
  288. }
  289. })
  290. }
  291. }
  292. func TestUpdateStateFile(t *testing.T) {
  293. testCases := []struct {
  294. description string
  295. expErr string
  296. expectedState *stateMemory
  297. }{
  298. {
  299. "Save empty state",
  300. "",
  301. &stateMemory{
  302. assignments: ContainerCPUAssignments{},
  303. defaultCPUSet: cpuset.NewCPUSet(),
  304. },
  305. },
  306. {
  307. "Save defaultCPUSet only",
  308. "",
  309. &stateMemory{
  310. assignments: ContainerCPUAssignments{},
  311. defaultCPUSet: cpuset.NewCPUSet(1, 6),
  312. },
  313. },
  314. {
  315. "Save assignments only",
  316. "",
  317. &stateMemory{
  318. assignments: ContainerCPUAssignments{
  319. "pod": map[string]cpuset.CPUSet{
  320. "container1": cpuset.NewCPUSet(4, 5, 6),
  321. "container2": cpuset.NewCPUSet(1, 2, 3),
  322. },
  323. },
  324. defaultCPUSet: cpuset.NewCPUSet(),
  325. },
  326. },
  327. }
  328. for idx, tc := range testCases {
  329. t.Run(tc.description, func(t *testing.T) {
  330. sfilePath, err := ioutil.TempFile("/tmp", fmt.Sprintf("cpumanager_state_file_test_%d", idx))
  331. defer os.Remove(sfilePath.Name())
  332. if err != nil {
  333. t.Errorf("cannot create temporary file: %q", err.Error())
  334. }
  335. fileState := stateFile{
  336. stateFilePath: sfilePath.Name(),
  337. policyName: "static",
  338. cache: NewMemoryState(),
  339. }
  340. fileState.SetDefaultCPUSet(tc.expectedState.defaultCPUSet)
  341. fileState.SetCPUAssignments(tc.expectedState.assignments)
  342. logData, _ := stderrCapture(t, func() State {
  343. fileState.storeState()
  344. return &stateFile{}
  345. })
  346. errMsg := logData.String()
  347. if tc.expErr != "" {
  348. if errMsg != "" {
  349. if errMsg != tc.expErr {
  350. t.Errorf("UpdateStateFile() error = %v, wantErr %v", errMsg, tc.expErr)
  351. return
  352. }
  353. } else {
  354. t.Errorf("UpdateStateFile() error = nil, wantErr %v", tc.expErr)
  355. return
  356. }
  357. } else {
  358. if errMsg != "" {
  359. t.Errorf("UpdateStateFile() error = %v, wantErr nil", errMsg)
  360. return
  361. }
  362. }
  363. newFileState, err := NewFileState(sfilePath.Name(), "static", nil)
  364. if err != nil {
  365. t.Errorf("NewFileState() error: %v", err)
  366. return
  367. }
  368. AssertStateEqual(t, newFileState, tc.expectedState)
  369. })
  370. }
  371. }
  372. func TestHelpersStateFile(t *testing.T) {
  373. testCases := []struct {
  374. description string
  375. defaultCPUset cpuset.CPUSet
  376. assignments map[string]map[string]cpuset.CPUSet
  377. }{
  378. {
  379. description: "one container",
  380. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  381. assignments: map[string]map[string]cpuset.CPUSet{
  382. "pod": {
  383. "c1": cpuset.NewCPUSet(0, 1),
  384. },
  385. },
  386. },
  387. {
  388. description: "two containers",
  389. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  390. assignments: map[string]map[string]cpuset.CPUSet{
  391. "pod": {
  392. "c1": cpuset.NewCPUSet(0, 1),
  393. "c2": cpuset.NewCPUSet(2, 3, 4, 5),
  394. },
  395. },
  396. },
  397. {
  398. description: "container with more cpus than is possible",
  399. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  400. assignments: map[string]map[string]cpuset.CPUSet{
  401. "pod": {
  402. "c1": cpuset.NewCPUSet(0, 10),
  403. },
  404. },
  405. },
  406. {
  407. description: "container without assigned cpus",
  408. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  409. assignments: map[string]map[string]cpuset.CPUSet{
  410. "pod": {
  411. "c1": cpuset.NewCPUSet(),
  412. },
  413. },
  414. },
  415. }
  416. for _, tc := range testCases {
  417. t.Run(tc.description, func(t *testing.T) {
  418. sfFile, err := ioutil.TempFile("/tmp", "testHelpersStateFile")
  419. defer os.Remove(sfFile.Name())
  420. if err != nil {
  421. t.Errorf("cannot create temporary test file: %q", err.Error())
  422. }
  423. state, err := NewFileState(sfFile.Name(), "static", nil)
  424. if err != nil {
  425. t.Errorf("new file state error: %v", err)
  426. return
  427. }
  428. state.SetDefaultCPUSet(tc.defaultCPUset)
  429. for podUID := range tc.assignments {
  430. for containerName, containerCPUs := range tc.assignments[podUID] {
  431. state.SetCPUSet(podUID, containerName, containerCPUs)
  432. if cpus, _ := state.GetCPUSet(podUID, containerName); !cpus.Equals(containerCPUs) {
  433. t.Errorf("state is inconsistent. Wants = %q Have = %q", containerCPUs, cpus)
  434. }
  435. state.Delete(podUID, containerName)
  436. if cpus := state.GetCPUSetOrDefault(podUID, containerName); !cpus.Equals(tc.defaultCPUset) {
  437. t.Error("deleted container still existing in state")
  438. }
  439. }
  440. }
  441. })
  442. }
  443. }
  444. func TestClearStateStateFile(t *testing.T) {
  445. testCases := []struct {
  446. description string
  447. defaultCPUset cpuset.CPUSet
  448. assignments map[string]map[string]cpuset.CPUSet
  449. }{
  450. {
  451. description: "valid file",
  452. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  453. assignments: map[string]map[string]cpuset.CPUSet{
  454. "pod": {
  455. "c1": cpuset.NewCPUSet(0, 1),
  456. "c2": cpuset.NewCPUSet(2, 3),
  457. "c3": cpuset.NewCPUSet(4, 5),
  458. },
  459. },
  460. },
  461. }
  462. for _, testCase := range testCases {
  463. t.Run(testCase.description, func(t *testing.T) {
  464. sfFile, err := ioutil.TempFile("/tmp", "testHelpersStateFile")
  465. defer os.Remove(sfFile.Name())
  466. if err != nil {
  467. t.Errorf("cannot create temporary test file: %q", err.Error())
  468. }
  469. state, err := NewFileState(sfFile.Name(), "static", nil)
  470. if err != nil {
  471. t.Errorf("new file state error: %v", err)
  472. return
  473. }
  474. state.SetDefaultCPUSet(testCase.defaultCPUset)
  475. for podUID := range testCase.assignments {
  476. for containerName, containerCPUs := range testCase.assignments[podUID] {
  477. state.SetCPUSet(podUID, containerName, containerCPUs)
  478. }
  479. }
  480. state.ClearState()
  481. if !cpuset.NewCPUSet().Equals(state.GetDefaultCPUSet()) {
  482. t.Error("cleared state shouldn't has got information about available cpuset")
  483. }
  484. for podUID := range testCase.assignments {
  485. for containerName := range testCase.assignments[podUID] {
  486. if !cpuset.NewCPUSet().Equals(state.GetCPUSetOrDefault(podUID, containerName)) {
  487. t.Error("cleared state shouldn't has got information about containers")
  488. }
  489. }
  490. }
  491. })
  492. }
  493. }