cpu_manager_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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 cpumanager
  14. import (
  15. "fmt"
  16. "reflect"
  17. "strings"
  18. "testing"
  19. "time"
  20. "io/ioutil"
  21. "os"
  22. cadvisorapi "github.com/google/cadvisor/info/v1"
  23. "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/api/resource"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/types"
  27. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  28. "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/state"
  29. "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/topology"
  30. "k8s.io/kubernetes/pkg/kubelet/cm/cpuset"
  31. )
  32. type mockState struct {
  33. assignments state.ContainerCPUAssignments
  34. defaultCPUSet cpuset.CPUSet
  35. }
  36. func (s *mockState) GetCPUSet(containerID string) (cpuset.CPUSet, bool) {
  37. res, ok := s.assignments[containerID]
  38. return res.Clone(), ok
  39. }
  40. func (s *mockState) GetDefaultCPUSet() cpuset.CPUSet {
  41. return s.defaultCPUSet.Clone()
  42. }
  43. func (s *mockState) GetCPUSetOrDefault(containerID string) cpuset.CPUSet {
  44. if res, ok := s.GetCPUSet(containerID); ok {
  45. return res
  46. }
  47. return s.GetDefaultCPUSet()
  48. }
  49. func (s *mockState) SetCPUSet(containerID string, cset cpuset.CPUSet) {
  50. s.assignments[containerID] = cset
  51. }
  52. func (s *mockState) SetDefaultCPUSet(cset cpuset.CPUSet) {
  53. s.defaultCPUSet = cset
  54. }
  55. func (s *mockState) Delete(containerID string) {
  56. delete(s.assignments, containerID)
  57. }
  58. func (s *mockState) ClearState() {
  59. s.defaultCPUSet = cpuset.CPUSet{}
  60. s.assignments = make(state.ContainerCPUAssignments)
  61. }
  62. func (s *mockState) SetCPUAssignments(a state.ContainerCPUAssignments) {
  63. s.assignments = a.Clone()
  64. }
  65. func (s *mockState) GetCPUAssignments() state.ContainerCPUAssignments {
  66. return s.assignments.Clone()
  67. }
  68. type mockPolicy struct {
  69. err error
  70. }
  71. func (p *mockPolicy) Name() string {
  72. return "mock"
  73. }
  74. func (p *mockPolicy) Start(s state.State) {
  75. }
  76. func (p *mockPolicy) AddContainer(s state.State, pod *v1.Pod, container *v1.Container, containerID string) error {
  77. return p.err
  78. }
  79. func (p *mockPolicy) RemoveContainer(s state.State, containerID string) error {
  80. return p.err
  81. }
  82. type mockRuntimeService struct {
  83. err error
  84. }
  85. func (rt mockRuntimeService) UpdateContainerResources(id string, resources *runtimeapi.LinuxContainerResources) error {
  86. return rt.err
  87. }
  88. type mockPodStatusProvider struct {
  89. podStatus v1.PodStatus
  90. found bool
  91. }
  92. func (psp mockPodStatusProvider) GetPodStatus(uid types.UID) (v1.PodStatus, bool) {
  93. return psp.podStatus, psp.found
  94. }
  95. func makePod(cpuRequest, cpuLimit string) *v1.Pod {
  96. return &v1.Pod{
  97. Spec: v1.PodSpec{
  98. Containers: []v1.Container{
  99. {
  100. Resources: v1.ResourceRequirements{
  101. Requests: v1.ResourceList{
  102. v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpuRequest),
  103. v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
  104. },
  105. Limits: v1.ResourceList{
  106. v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpuLimit),
  107. v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
  108. },
  109. },
  110. },
  111. },
  112. },
  113. }
  114. }
  115. func TestCPUManagerAdd(t *testing.T) {
  116. testPolicy := NewStaticPolicy(
  117. &topology.CPUTopology{
  118. NumCPUs: 4,
  119. NumSockets: 1,
  120. NumCores: 4,
  121. CPUDetails: map[int]topology.CPUInfo{
  122. 0: {CoreID: 0, SocketID: 0},
  123. 1: {CoreID: 1, SocketID: 0},
  124. 2: {CoreID: 2, SocketID: 0},
  125. 3: {CoreID: 3, SocketID: 0},
  126. },
  127. }, 0)
  128. testCases := []struct {
  129. description string
  130. updateErr error
  131. policy Policy
  132. expCPUSet cpuset.CPUSet
  133. expErr error
  134. }{
  135. {
  136. description: "cpu manager add - no error",
  137. updateErr: nil,
  138. policy: testPolicy,
  139. expCPUSet: cpuset.NewCPUSet(3, 4),
  140. expErr: nil,
  141. },
  142. {
  143. description: "cpu manager add - policy add container error",
  144. updateErr: nil,
  145. policy: &mockPolicy{
  146. err: fmt.Errorf("fake reg error"),
  147. },
  148. expCPUSet: cpuset.NewCPUSet(1, 2, 3, 4),
  149. expErr: fmt.Errorf("fake reg error"),
  150. },
  151. {
  152. description: "cpu manager add - container update error",
  153. updateErr: fmt.Errorf("fake update error"),
  154. policy: testPolicy,
  155. expCPUSet: cpuset.NewCPUSet(1, 2, 3, 4),
  156. expErr: fmt.Errorf("fake update error"),
  157. },
  158. }
  159. for _, testCase := range testCases {
  160. mgr := &manager{
  161. policy: testCase.policy,
  162. state: &mockState{
  163. assignments: state.ContainerCPUAssignments{},
  164. defaultCPUSet: cpuset.NewCPUSet(1, 2, 3, 4),
  165. },
  166. containerRuntime: mockRuntimeService{
  167. err: testCase.updateErr,
  168. },
  169. activePods: func() []*v1.Pod { return nil },
  170. podStatusProvider: mockPodStatusProvider{},
  171. }
  172. pod := makePod("2", "2")
  173. container := &pod.Spec.Containers[0]
  174. err := mgr.AddContainer(pod, container, "fakeID")
  175. if !reflect.DeepEqual(err, testCase.expErr) {
  176. t.Errorf("CPU Manager AddContainer() error (%v). expected error: %v but got: %v",
  177. testCase.description, testCase.expErr, err)
  178. }
  179. if !testCase.expCPUSet.Equals(mgr.state.GetDefaultCPUSet()) {
  180. t.Errorf("CPU Manager AddContainer() error (%v). expected cpuset: %v but got: %v",
  181. testCase.description, testCase.expCPUSet, mgr.state.GetDefaultCPUSet())
  182. }
  183. }
  184. }
  185. func TestCPUManagerGenerate(t *testing.T) {
  186. testCases := []struct {
  187. description string
  188. cpuPolicyName string
  189. nodeAllocatableReservation v1.ResourceList
  190. isTopologyBroken bool
  191. expectedPolicy string
  192. expectedError error
  193. }{
  194. {
  195. description: "set none policy",
  196. cpuPolicyName: "none",
  197. nodeAllocatableReservation: nil,
  198. expectedPolicy: "none",
  199. },
  200. {
  201. description: "invalid policy name",
  202. cpuPolicyName: "invalid",
  203. nodeAllocatableReservation: nil,
  204. expectedPolicy: "none",
  205. },
  206. {
  207. description: "static policy",
  208. cpuPolicyName: "static",
  209. nodeAllocatableReservation: v1.ResourceList{v1.ResourceCPU: *resource.NewQuantity(3, resource.DecimalSI)},
  210. expectedPolicy: "static",
  211. },
  212. {
  213. description: "static policy - broken topology",
  214. cpuPolicyName: "static",
  215. nodeAllocatableReservation: v1.ResourceList{},
  216. isTopologyBroken: true,
  217. expectedError: fmt.Errorf("could not detect number of cpus"),
  218. },
  219. {
  220. description: "static policy - broken reservation",
  221. cpuPolicyName: "static",
  222. nodeAllocatableReservation: v1.ResourceList{},
  223. expectedError: fmt.Errorf("unable to determine reserved CPU resources for static policy"),
  224. },
  225. {
  226. description: "static policy - no CPU resources",
  227. cpuPolicyName: "static",
  228. nodeAllocatableReservation: v1.ResourceList{v1.ResourceCPU: *resource.NewQuantity(0, resource.DecimalSI)},
  229. expectedError: fmt.Errorf("the static policy requires systemreserved.cpu + kubereserved.cpu to be greater than zero"),
  230. },
  231. }
  232. mockedMachineInfo := cadvisorapi.MachineInfo{
  233. NumCores: 4,
  234. Topology: []cadvisorapi.Node{
  235. {
  236. Cores: []cadvisorapi.Core{
  237. {
  238. Id: 0,
  239. Threads: []int{0},
  240. },
  241. {
  242. Id: 1,
  243. Threads: []int{1},
  244. },
  245. {
  246. Id: 2,
  247. Threads: []int{2},
  248. },
  249. {
  250. Id: 3,
  251. Threads: []int{3},
  252. },
  253. },
  254. },
  255. },
  256. }
  257. for _, testCase := range testCases {
  258. t.Run(testCase.description, func(t *testing.T) {
  259. machineInfo := &mockedMachineInfo
  260. if testCase.isTopologyBroken {
  261. machineInfo = &cadvisorapi.MachineInfo{}
  262. }
  263. sDir, err := ioutil.TempDir("/tmp/", "cpu_manager_test")
  264. if err != nil {
  265. t.Errorf("cannot create state file: %s", err.Error())
  266. }
  267. defer os.RemoveAll(sDir)
  268. mgr, err := NewManager(testCase.cpuPolicyName, 5*time.Second, machineInfo, testCase.nodeAllocatableReservation, sDir)
  269. if testCase.expectedError != nil {
  270. if !strings.Contains(err.Error(), testCase.expectedError.Error()) {
  271. t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), testCase.expectedError.Error())
  272. }
  273. } else {
  274. rawMgr := mgr.(*manager)
  275. if rawMgr.policy.Name() != testCase.expectedPolicy {
  276. t.Errorf("Unexpected policy name. Have: %q wants %q", rawMgr.policy.Name(), testCase.expectedPolicy)
  277. }
  278. }
  279. })
  280. }
  281. }
  282. func TestCPUManagerRemove(t *testing.T) {
  283. mgr := &manager{
  284. policy: &mockPolicy{
  285. err: nil,
  286. },
  287. state: &mockState{
  288. assignments: state.ContainerCPUAssignments{},
  289. defaultCPUSet: cpuset.NewCPUSet(),
  290. },
  291. containerRuntime: mockRuntimeService{},
  292. activePods: func() []*v1.Pod { return nil },
  293. podStatusProvider: mockPodStatusProvider{},
  294. }
  295. err := mgr.RemoveContainer("fakeID")
  296. if err != nil {
  297. t.Errorf("CPU Manager RemoveContainer() error. expected error to be nil but got: %v", err)
  298. }
  299. mgr = &manager{
  300. policy: &mockPolicy{
  301. err: fmt.Errorf("fake error"),
  302. },
  303. state: state.NewMemoryState(),
  304. containerRuntime: mockRuntimeService{},
  305. activePods: func() []*v1.Pod { return nil },
  306. podStatusProvider: mockPodStatusProvider{},
  307. }
  308. err = mgr.RemoveContainer("fakeID")
  309. if !reflect.DeepEqual(err, fmt.Errorf("fake error")) {
  310. t.Errorf("CPU Manager RemoveContainer() error. expected error: fake error but got: %v", err)
  311. }
  312. }
  313. func TestReconcileState(t *testing.T) {
  314. testCases := []struct {
  315. description string
  316. activePods []*v1.Pod
  317. pspPS v1.PodStatus
  318. pspFound bool
  319. stAssignments state.ContainerCPUAssignments
  320. stDefaultCPUSet cpuset.CPUSet
  321. updateErr error
  322. expectSucceededContainerName string
  323. expectFailedContainerName string
  324. }{
  325. {
  326. description: "cpu manager reconclie - no error",
  327. activePods: []*v1.Pod{
  328. {
  329. ObjectMeta: metav1.ObjectMeta{
  330. Name: "fakePodName",
  331. UID: "fakeUID",
  332. },
  333. Spec: v1.PodSpec{
  334. Containers: []v1.Container{
  335. {
  336. Name: "fakeName",
  337. },
  338. },
  339. },
  340. },
  341. },
  342. pspPS: v1.PodStatus{
  343. ContainerStatuses: []v1.ContainerStatus{
  344. {
  345. Name: "fakeName",
  346. ContainerID: "docker://fakeID",
  347. },
  348. },
  349. },
  350. pspFound: true,
  351. stAssignments: state.ContainerCPUAssignments{
  352. "fakeID": cpuset.NewCPUSet(1, 2),
  353. },
  354. stDefaultCPUSet: cpuset.NewCPUSet(3, 4, 5, 6, 7),
  355. updateErr: nil,
  356. expectSucceededContainerName: "fakeName",
  357. expectFailedContainerName: "",
  358. },
  359. {
  360. description: "cpu manager reconcile init container - no error",
  361. activePods: []*v1.Pod{
  362. {
  363. ObjectMeta: metav1.ObjectMeta{
  364. Name: "fakePodName",
  365. UID: "fakeUID",
  366. },
  367. Spec: v1.PodSpec{
  368. InitContainers: []v1.Container{
  369. {
  370. Name: "fakeName",
  371. },
  372. },
  373. },
  374. },
  375. },
  376. pspPS: v1.PodStatus{
  377. InitContainerStatuses: []v1.ContainerStatus{
  378. {
  379. Name: "fakeName",
  380. ContainerID: "docker://fakeID",
  381. },
  382. },
  383. },
  384. pspFound: true,
  385. stAssignments: state.ContainerCPUAssignments{
  386. "fakeID": cpuset.NewCPUSet(1, 2),
  387. },
  388. stDefaultCPUSet: cpuset.NewCPUSet(3, 4, 5, 6, 7),
  389. updateErr: nil,
  390. expectSucceededContainerName: "fakeName",
  391. expectFailedContainerName: "",
  392. },
  393. {
  394. description: "cpu manager reconclie - pod status not found",
  395. activePods: []*v1.Pod{
  396. {
  397. ObjectMeta: metav1.ObjectMeta{
  398. Name: "fakePodName",
  399. UID: "fakeUID",
  400. },
  401. Spec: v1.PodSpec{
  402. Containers: []v1.Container{
  403. {
  404. Name: "fakeName",
  405. },
  406. },
  407. },
  408. },
  409. },
  410. pspPS: v1.PodStatus{},
  411. pspFound: false,
  412. stAssignments: state.ContainerCPUAssignments{},
  413. stDefaultCPUSet: cpuset.NewCPUSet(),
  414. updateErr: nil,
  415. expectSucceededContainerName: "",
  416. expectFailedContainerName: "fakeName",
  417. },
  418. {
  419. description: "cpu manager reconclie - container id not found",
  420. activePods: []*v1.Pod{
  421. {
  422. ObjectMeta: metav1.ObjectMeta{
  423. Name: "fakePodName",
  424. UID: "fakeUID",
  425. },
  426. Spec: v1.PodSpec{
  427. Containers: []v1.Container{
  428. {
  429. Name: "fakeName",
  430. },
  431. },
  432. },
  433. },
  434. },
  435. pspPS: v1.PodStatus{
  436. ContainerStatuses: []v1.ContainerStatus{
  437. {
  438. Name: "fakeName1",
  439. ContainerID: "docker://fakeID",
  440. },
  441. },
  442. },
  443. pspFound: true,
  444. stAssignments: state.ContainerCPUAssignments{},
  445. stDefaultCPUSet: cpuset.NewCPUSet(),
  446. updateErr: nil,
  447. expectSucceededContainerName: "",
  448. expectFailedContainerName: "fakeName",
  449. },
  450. {
  451. description: "cpu manager reconclie - cpuset is empty",
  452. activePods: []*v1.Pod{
  453. {
  454. ObjectMeta: metav1.ObjectMeta{
  455. Name: "fakePodName",
  456. UID: "fakeUID",
  457. },
  458. Spec: v1.PodSpec{
  459. Containers: []v1.Container{
  460. {
  461. Name: "fakeName",
  462. },
  463. },
  464. },
  465. },
  466. },
  467. pspPS: v1.PodStatus{
  468. ContainerStatuses: []v1.ContainerStatus{
  469. {
  470. Name: "fakeName",
  471. ContainerID: "docker://fakeID",
  472. },
  473. },
  474. },
  475. pspFound: true,
  476. stAssignments: state.ContainerCPUAssignments{
  477. "fakeID": cpuset.NewCPUSet(),
  478. },
  479. stDefaultCPUSet: cpuset.NewCPUSet(1, 2, 3, 4, 5, 6, 7),
  480. updateErr: nil,
  481. expectSucceededContainerName: "",
  482. expectFailedContainerName: "fakeName",
  483. },
  484. {
  485. description: "cpu manager reconclie - container update error",
  486. activePods: []*v1.Pod{
  487. {
  488. ObjectMeta: metav1.ObjectMeta{
  489. Name: "fakePodName",
  490. UID: "fakeUID",
  491. },
  492. Spec: v1.PodSpec{
  493. Containers: []v1.Container{
  494. {
  495. Name: "fakeName",
  496. },
  497. },
  498. },
  499. },
  500. },
  501. pspPS: v1.PodStatus{
  502. ContainerStatuses: []v1.ContainerStatus{
  503. {
  504. Name: "fakeName",
  505. ContainerID: "docker://fakeID",
  506. },
  507. },
  508. },
  509. pspFound: true,
  510. stAssignments: state.ContainerCPUAssignments{
  511. "fakeID": cpuset.NewCPUSet(1, 2),
  512. },
  513. stDefaultCPUSet: cpuset.NewCPUSet(3, 4, 5, 6, 7),
  514. updateErr: fmt.Errorf("fake container update error"),
  515. expectSucceededContainerName: "",
  516. expectFailedContainerName: "fakeName",
  517. },
  518. }
  519. for _, testCase := range testCases {
  520. mgr := &manager{
  521. policy: &mockPolicy{
  522. err: nil,
  523. },
  524. state: &mockState{
  525. assignments: testCase.stAssignments,
  526. defaultCPUSet: testCase.stDefaultCPUSet,
  527. },
  528. containerRuntime: mockRuntimeService{
  529. err: testCase.updateErr,
  530. },
  531. activePods: func() []*v1.Pod {
  532. return testCase.activePods
  533. },
  534. podStatusProvider: mockPodStatusProvider{
  535. podStatus: testCase.pspPS,
  536. found: testCase.pspFound,
  537. },
  538. }
  539. success, failure := mgr.reconcileState()
  540. if testCase.expectSucceededContainerName != "" {
  541. // Search succeeded reconciled containers for the supplied name.
  542. foundSucceededContainer := false
  543. for _, reconciled := range success {
  544. if reconciled.containerName == testCase.expectSucceededContainerName {
  545. foundSucceededContainer = true
  546. break
  547. }
  548. }
  549. if !foundSucceededContainer {
  550. t.Errorf("%v", testCase.description)
  551. t.Errorf("Expected reconciliation success for container: %s", testCase.expectSucceededContainerName)
  552. }
  553. }
  554. if testCase.expectFailedContainerName != "" {
  555. // Search failed reconciled containers for the supplied name.
  556. foundFailedContainer := false
  557. for _, reconciled := range failure {
  558. if reconciled.containerName == testCase.expectFailedContainerName {
  559. foundFailedContainer = true
  560. break
  561. }
  562. }
  563. if !foundFailedContainer {
  564. t.Errorf("%v", testCase.description)
  565. t.Errorf("Expected reconciliation failure for container: %s", testCase.expectFailedContainerName)
  566. }
  567. }
  568. }
  569. }