state_file_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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/cpuset"
  25. )
  26. func writeToStateFile(statefile string, content string) {
  27. ioutil.WriteFile(statefile, []byte(content), 0644)
  28. }
  29. // AssertStateEqual marks provided test as failed if provided states differ
  30. func AssertStateEqual(t *testing.T, sf State, sm State) {
  31. cpusetSf := sf.GetDefaultCPUSet()
  32. cpusetSm := sm.GetDefaultCPUSet()
  33. if !cpusetSf.Equals(cpusetSm) {
  34. t.Errorf("State CPUSet mismatch. Have %v, want %v", cpusetSf, cpusetSm)
  35. }
  36. cpuassignmentSf := sf.GetCPUAssignments()
  37. cpuassignmentSm := sm.GetCPUAssignments()
  38. if !reflect.DeepEqual(cpuassignmentSf, cpuassignmentSm) {
  39. t.Errorf("State CPU assignments mismatch. Have %s, want %s", cpuassignmentSf, cpuassignmentSm)
  40. }
  41. }
  42. func stderrCapture(t *testing.T, f func() State) (bytes.Buffer, State) {
  43. stderr := os.Stderr
  44. readBuffer, writeBuffer, err := os.Pipe()
  45. if err != nil {
  46. t.Errorf("cannot create pipe: %v", err.Error())
  47. }
  48. os.Stderr = writeBuffer
  49. var outputBuffer bytes.Buffer
  50. state := f()
  51. writeBuffer.Close()
  52. io.Copy(&outputBuffer, readBuffer)
  53. os.Stderr = stderr
  54. return outputBuffer, state
  55. }
  56. func TestFileStateTryRestore(t *testing.T) {
  57. testCases := []struct {
  58. description string
  59. stateFileContent string
  60. policyName string
  61. expErr string
  62. expPanic bool
  63. expectedState *stateMemory
  64. }{
  65. {
  66. "Invalid JSON - one byte file",
  67. "\n",
  68. "none",
  69. "[cpumanager] state file: unable to restore state from disk (unexpected end of JSON input)",
  70. true,
  71. &stateMemory{},
  72. },
  73. {
  74. "Invalid JSON - invalid content",
  75. "{",
  76. "none",
  77. "[cpumanager] state file: unable to restore state from disk (unexpected end of JSON input)",
  78. true,
  79. &stateMemory{},
  80. },
  81. {
  82. "Try restore defaultCPUSet only",
  83. `{"policyName": "none", "defaultCpuSet": "4-6"}`,
  84. "none",
  85. "",
  86. false,
  87. &stateMemory{
  88. assignments: ContainerCPUAssignments{},
  89. defaultCPUSet: cpuset.NewCPUSet(4, 5, 6),
  90. },
  91. },
  92. {
  93. "Try restore defaultCPUSet only - invalid name",
  94. `{"policyName": "none", "defaultCpuSet" "4-6"}`,
  95. "none",
  96. `[cpumanager] state file: unable to restore state from disk (invalid character '"' after object key)`,
  97. true,
  98. &stateMemory{},
  99. },
  100. {
  101. "Try restore assignments only",
  102. `{
  103. "policyName": "none",
  104. "entries": {
  105. "container1": "4-6",
  106. "container2": "1-3"
  107. }
  108. }`,
  109. "none",
  110. "",
  111. false,
  112. &stateMemory{
  113. assignments: ContainerCPUAssignments{
  114. "container1": cpuset.NewCPUSet(4, 5, 6),
  115. "container2": cpuset.NewCPUSet(1, 2, 3),
  116. },
  117. defaultCPUSet: cpuset.NewCPUSet(),
  118. },
  119. },
  120. {
  121. "Try restore invalid policy name",
  122. `{
  123. "policyName": "A",
  124. "defaultCpuSet": "0-7",
  125. "entries": {}
  126. }`,
  127. "B",
  128. `[cpumanager] state file: unable to restore state from disk (policy configured "B" != policy from state file "A")`,
  129. true,
  130. &stateMemory{},
  131. },
  132. {
  133. "Try restore invalid assignments",
  134. `{"entries": }`,
  135. "none",
  136. "[cpumanager] state file: unable to restore state from disk (invalid character '}' looking for beginning of value)",
  137. true,
  138. &stateMemory{},
  139. },
  140. {
  141. "Try restore valid file",
  142. `{
  143. "policyName": "none",
  144. "defaultCpuSet": "23-24",
  145. "entries": {
  146. "container1": "4-6",
  147. "container2": "1-3"
  148. }
  149. }`,
  150. "none",
  151. "",
  152. false,
  153. &stateMemory{
  154. assignments: ContainerCPUAssignments{
  155. "container1": cpuset.NewCPUSet(4, 5, 6),
  156. "container2": cpuset.NewCPUSet(1, 2, 3),
  157. },
  158. defaultCPUSet: cpuset.NewCPUSet(23, 24),
  159. },
  160. },
  161. {
  162. "Try restore un-parsable defaultCPUSet ",
  163. `{
  164. "policyName": "none",
  165. "defaultCpuSet": "2-sd"
  166. }`,
  167. "none",
  168. `[cpumanager] state file: unable to restore state from disk (strconv.Atoi: parsing "sd": invalid syntax)`,
  169. true,
  170. &stateMemory{},
  171. },
  172. {
  173. "Try restore un-parsable assignments",
  174. `{
  175. "policyName": "none",
  176. "defaultCpuSet": "23-24",
  177. "entries": {
  178. "container1": "p-6",
  179. "container2": "1-3"
  180. }
  181. }`,
  182. "none",
  183. `[cpumanager] state file: unable to restore state from disk (strconv.Atoi: parsing "p": invalid syntax)`,
  184. true,
  185. &stateMemory{},
  186. },
  187. {
  188. "tryRestoreState creates empty state file",
  189. "",
  190. "none",
  191. "",
  192. false,
  193. &stateMemory{
  194. assignments: ContainerCPUAssignments{},
  195. defaultCPUSet: cpuset.NewCPUSet(),
  196. },
  197. },
  198. }
  199. for idx, tc := range testCases {
  200. t.Run(tc.description, func(t *testing.T) {
  201. defer func() {
  202. if tc.expPanic {
  203. r := recover()
  204. panicMsg := r.(string)
  205. if !strings.HasPrefix(panicMsg, tc.expErr) {
  206. t.Fatalf(`expected panic "%s" but got "%s"`, tc.expErr, panicMsg)
  207. } else {
  208. t.Logf(`got expected panic "%s"`, panicMsg)
  209. }
  210. }
  211. }()
  212. sfilePath, err := ioutil.TempFile("/tmp", fmt.Sprintf("cpumanager_state_file_test_%d", idx))
  213. if err != nil {
  214. t.Errorf("cannot create temporary file: %q", err.Error())
  215. }
  216. // Don't create state file, let tryRestoreState figure out that is should create
  217. if tc.stateFileContent != "" {
  218. writeToStateFile(sfilePath.Name(), tc.stateFileContent)
  219. }
  220. // Always remove file - regardless of who created
  221. defer os.Remove(sfilePath.Name())
  222. logData, fileState := stderrCapture(t, func() State {
  223. return NewFileState(sfilePath.Name(), tc.policyName)
  224. })
  225. if tc.expErr != "" {
  226. if logData.String() != "" {
  227. if !strings.Contains(logData.String(), tc.expErr) {
  228. t.Errorf("tryRestoreState() error = %v, wantErr %v", logData.String(), tc.expErr)
  229. return
  230. }
  231. } else {
  232. t.Errorf("tryRestoreState() error = nil, wantErr %v", tc.expErr)
  233. return
  234. }
  235. }
  236. AssertStateEqual(t, fileState, tc.expectedState)
  237. })
  238. }
  239. }
  240. func TestFileStateTryRestorePanic(t *testing.T) {
  241. testCase := struct {
  242. description string
  243. wantPanic bool
  244. panicMessage string
  245. }{
  246. "Panic creating file",
  247. true,
  248. "[cpumanager] state file not written",
  249. }
  250. t.Run(testCase.description, func(t *testing.T) {
  251. sfilePath := path.Join("/invalid_path/to_some_dir", "cpumanager_state_file_test")
  252. defer func() {
  253. if err := recover(); err != nil {
  254. if testCase.wantPanic {
  255. if testCase.panicMessage == err {
  256. t.Logf("tryRestoreState() got expected panic = %v", err)
  257. return
  258. }
  259. t.Errorf("tryRestoreState() unexpected panic = %v, wantErr %v", err, testCase.panicMessage)
  260. }
  261. }
  262. }()
  263. NewFileState(sfilePath, "static")
  264. })
  265. }
  266. func TestUpdateStateFile(t *testing.T) {
  267. testCases := []struct {
  268. description string
  269. expErr string
  270. expectedState *stateMemory
  271. }{
  272. {
  273. "Save empty state",
  274. "",
  275. &stateMemory{
  276. assignments: ContainerCPUAssignments{},
  277. defaultCPUSet: cpuset.NewCPUSet(),
  278. },
  279. },
  280. {
  281. "Save defaultCPUSet only",
  282. "",
  283. &stateMemory{
  284. assignments: ContainerCPUAssignments{},
  285. defaultCPUSet: cpuset.NewCPUSet(1, 6),
  286. },
  287. },
  288. {
  289. "Save assignments only",
  290. "",
  291. &stateMemory{
  292. assignments: ContainerCPUAssignments{
  293. "container1": cpuset.NewCPUSet(4, 5, 6),
  294. "container2": cpuset.NewCPUSet(1, 2, 3),
  295. },
  296. defaultCPUSet: cpuset.NewCPUSet(),
  297. },
  298. },
  299. }
  300. for idx, tc := range testCases {
  301. t.Run(tc.description, func(t *testing.T) {
  302. sfilePath, err := ioutil.TempFile("/tmp", fmt.Sprintf("cpumanager_state_file_test_%d", idx))
  303. defer os.Remove(sfilePath.Name())
  304. if err != nil {
  305. t.Errorf("cannot create temporary file: %q", err.Error())
  306. }
  307. fileState := stateFile{
  308. stateFilePath: sfilePath.Name(),
  309. policyName: "static",
  310. cache: NewMemoryState(),
  311. }
  312. fileState.SetDefaultCPUSet(tc.expectedState.defaultCPUSet)
  313. fileState.SetCPUAssignments(tc.expectedState.assignments)
  314. logData, _ := stderrCapture(t, func() State {
  315. fileState.storeState()
  316. return &stateFile{}
  317. })
  318. errMsg := logData.String()
  319. if tc.expErr != "" {
  320. if errMsg != "" {
  321. if errMsg != tc.expErr {
  322. t.Errorf("UpdateStateFile() error = %v, wantErr %v", errMsg, tc.expErr)
  323. return
  324. }
  325. } else {
  326. t.Errorf("UpdateStateFile() error = nil, wantErr %v", tc.expErr)
  327. return
  328. }
  329. } else {
  330. if errMsg != "" {
  331. t.Errorf("UpdateStateFile() error = %v, wantErr nil", errMsg)
  332. return
  333. }
  334. }
  335. newFileState := NewFileState(sfilePath.Name(), "static")
  336. AssertStateEqual(t, newFileState, tc.expectedState)
  337. })
  338. }
  339. }
  340. func TestHelpersStateFile(t *testing.T) {
  341. testCases := []struct {
  342. description string
  343. defaultCPUset cpuset.CPUSet
  344. containers map[string]cpuset.CPUSet
  345. }{
  346. {
  347. description: "one container",
  348. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  349. containers: map[string]cpuset.CPUSet{
  350. "c1": cpuset.NewCPUSet(0, 1),
  351. },
  352. },
  353. {
  354. description: "two containers",
  355. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  356. containers: map[string]cpuset.CPUSet{
  357. "c1": cpuset.NewCPUSet(0, 1),
  358. "c2": cpuset.NewCPUSet(2, 3, 4, 5),
  359. },
  360. },
  361. {
  362. description: "container with more cpus than is possible",
  363. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  364. containers: map[string]cpuset.CPUSet{
  365. "c1": cpuset.NewCPUSet(0, 10),
  366. },
  367. },
  368. {
  369. description: "container without assigned cpus",
  370. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  371. containers: map[string]cpuset.CPUSet{
  372. "c1": cpuset.NewCPUSet(),
  373. },
  374. },
  375. }
  376. for _, tc := range testCases {
  377. t.Run(tc.description, func(t *testing.T) {
  378. sfFile, err := ioutil.TempFile("/tmp", "testHelpersStateFile")
  379. defer os.Remove(sfFile.Name())
  380. if err != nil {
  381. t.Errorf("cannot create temporary test file: %q", err.Error())
  382. }
  383. state := NewFileState(sfFile.Name(), "static")
  384. state.SetDefaultCPUSet(tc.defaultCPUset)
  385. for containerName, containerCPUs := range tc.containers {
  386. state.SetCPUSet(containerName, containerCPUs)
  387. if cpus, _ := state.GetCPUSet(containerName); !cpus.Equals(containerCPUs) {
  388. t.Errorf("state is inconsistent. Wants = %q Have = %q", containerCPUs, cpus)
  389. }
  390. state.Delete(containerName)
  391. if cpus := state.GetCPUSetOrDefault(containerName); !cpus.Equals(tc.defaultCPUset) {
  392. t.Error("deleted container still existing in state")
  393. }
  394. }
  395. })
  396. }
  397. }
  398. func TestClearStateStateFile(t *testing.T) {
  399. testCases := []struct {
  400. description string
  401. defaultCPUset cpuset.CPUSet
  402. containers map[string]cpuset.CPUSet
  403. }{
  404. {
  405. description: "valid file",
  406. defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
  407. containers: map[string]cpuset.CPUSet{
  408. "c1": cpuset.NewCPUSet(0, 1),
  409. "c2": cpuset.NewCPUSet(2, 3),
  410. "c3": cpuset.NewCPUSet(4, 5),
  411. },
  412. },
  413. }
  414. for _, testCase := range testCases {
  415. t.Run(testCase.description, func(t *testing.T) {
  416. sfFile, err := ioutil.TempFile("/tmp", "testHelpersStateFile")
  417. defer os.Remove(sfFile.Name())
  418. if err != nil {
  419. t.Errorf("cannot create temporary test file: %q", err.Error())
  420. }
  421. state := NewFileState(sfFile.Name(), "static")
  422. state.SetDefaultCPUSet(testCase.defaultCPUset)
  423. for containerName, containerCPUs := range testCase.containers {
  424. state.SetCPUSet(containerName, containerCPUs)
  425. }
  426. state.ClearState()
  427. if !cpuset.NewCPUSet().Equals(state.GetDefaultCPUSet()) {
  428. t.Error("cleared state shoudn't has got information about available cpuset")
  429. }
  430. for containerName := range testCase.containers {
  431. if !cpuset.NewCPUSet().Equals(state.GetCPUSetOrDefault(containerName)) {
  432. t.Error("cleared state shoudn't has got information about containers")
  433. }
  434. }
  435. })
  436. }
  437. }