kubelet_node_status_test.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080
  1. /*
  2. Copyright 2016 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 kubelet
  14. import (
  15. "fmt"
  16. "reflect"
  17. goruntime "runtime"
  18. "sort"
  19. "strconv"
  20. "testing"
  21. "time"
  22. cadvisorapi "github.com/google/cadvisor/info/v1"
  23. cadvisorapiv2 "github.com/google/cadvisor/info/v2"
  24. "k8s.io/kubernetes/pkg/api"
  25. apierrors "k8s.io/kubernetes/pkg/api/errors"
  26. "k8s.io/kubernetes/pkg/api/resource"
  27. "k8s.io/kubernetes/pkg/api/unversioned"
  28. "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
  29. "k8s.io/kubernetes/pkg/client/testing/core"
  30. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  31. "k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
  32. "k8s.io/kubernetes/pkg/runtime"
  33. "k8s.io/kubernetes/pkg/util/diff"
  34. "k8s.io/kubernetes/pkg/util/rand"
  35. "k8s.io/kubernetes/pkg/util/uuid"
  36. "k8s.io/kubernetes/pkg/util/wait"
  37. "k8s.io/kubernetes/pkg/version"
  38. "k8s.io/kubernetes/pkg/volume/util/volumehelper"
  39. )
  40. // generateTestingImageList generate randomly generated image list and corresponding expectedImageList.
  41. func generateTestingImageList(count int) ([]kubecontainer.Image, []api.ContainerImage) {
  42. // imageList is randomly generated image list
  43. var imageList []kubecontainer.Image
  44. for ; count > 0; count-- {
  45. imageItem := kubecontainer.Image{
  46. ID: string(uuid.NewUUID()),
  47. RepoTags: generateImageTags(),
  48. Size: rand.Int63nRange(minImgSize, maxImgSize+1),
  49. }
  50. imageList = append(imageList, imageItem)
  51. }
  52. // expectedImageList is generated by imageList according to size and maxImagesInNodeStatus
  53. // 1. sort the imageList by size
  54. sort.Sort(sliceutils.ByImageSize(imageList))
  55. // 2. convert sorted imageList to api.ContainerImage list
  56. var expectedImageList []api.ContainerImage
  57. for _, kubeImage := range imageList {
  58. apiImage := api.ContainerImage{
  59. Names: kubeImage.RepoTags,
  60. SizeBytes: kubeImage.Size,
  61. }
  62. expectedImageList = append(expectedImageList, apiImage)
  63. }
  64. // 3. only returns the top maxImagesInNodeStatus images in expectedImageList
  65. return imageList, expectedImageList[0:maxImagesInNodeStatus]
  66. }
  67. func generateImageTags() []string {
  68. var tagList []string
  69. count := rand.IntnRange(1, maxImageTagsForTest+1)
  70. for ; count > 0; count-- {
  71. tagList = append(tagList, "gcr.io/google_containers:v"+strconv.Itoa(count))
  72. }
  73. return tagList
  74. }
  75. func TestUpdateNewNodeStatus(t *testing.T) {
  76. // generate one more than maxImagesInNodeStatus in inputImageList
  77. inputImageList, expectedImageList := generateTestingImageList(maxImagesInNodeStatus + 1)
  78. testKubelet := newTestKubeletWithImageList(
  79. t, inputImageList, false /* controllerAttachDetachEnabled */)
  80. kubelet := testKubelet.kubelet
  81. kubeClient := testKubelet.fakeKubeClient
  82. kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{
  83. {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}},
  84. }}).ReactionChain
  85. machineInfo := &cadvisorapi.MachineInfo{
  86. MachineID: "123",
  87. SystemUUID: "abc",
  88. BootID: "1b3",
  89. NumCores: 2,
  90. MemoryCapacity: 10E9, // 10G
  91. }
  92. mockCadvisor := testKubelet.fakeCadvisor
  93. mockCadvisor.On("Start").Return(nil)
  94. mockCadvisor.On("MachineInfo").Return(machineInfo, nil)
  95. versionInfo := &cadvisorapi.VersionInfo{
  96. KernelVersion: "3.16.0-0.bpo.4-amd64",
  97. ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)",
  98. }
  99. mockCadvisor.On("VersionInfo").Return(versionInfo, nil)
  100. // Make kubelet report that it has sufficient disk space.
  101. if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, 200, 200, 100, 100); err != nil {
  102. t.Fatalf("can't update disk space manager: %v", err)
  103. }
  104. expectedNode := &api.Node{
  105. ObjectMeta: api.ObjectMeta{Name: testKubeletHostname},
  106. Spec: api.NodeSpec{},
  107. Status: api.NodeStatus{
  108. Conditions: []api.NodeCondition{
  109. {
  110. Type: api.NodeOutOfDisk,
  111. Status: api.ConditionFalse,
  112. Reason: "KubeletHasSufficientDisk",
  113. Message: fmt.Sprintf("kubelet has sufficient disk space available"),
  114. LastHeartbeatTime: unversioned.Time{},
  115. LastTransitionTime: unversioned.Time{},
  116. },
  117. {
  118. Type: api.NodeMemoryPressure,
  119. Status: api.ConditionFalse,
  120. Reason: "KubeletHasSufficientMemory",
  121. Message: fmt.Sprintf("kubelet has sufficient memory available"),
  122. LastHeartbeatTime: unversioned.Time{},
  123. LastTransitionTime: unversioned.Time{},
  124. },
  125. {
  126. Type: api.NodeDiskPressure,
  127. Status: api.ConditionFalse,
  128. Reason: "KubeletHasNoDiskPressure",
  129. Message: fmt.Sprintf("kubelet has no disk pressure"),
  130. LastHeartbeatTime: unversioned.Time{},
  131. LastTransitionTime: unversioned.Time{},
  132. },
  133. {
  134. Type: api.NodeReady,
  135. Status: api.ConditionTrue,
  136. Reason: "KubeletReady",
  137. Message: fmt.Sprintf("kubelet is posting ready status"),
  138. LastHeartbeatTime: unversioned.Time{},
  139. LastTransitionTime: unversioned.Time{},
  140. },
  141. },
  142. NodeInfo: api.NodeSystemInfo{
  143. MachineID: "123",
  144. SystemUUID: "abc",
  145. BootID: "1b3",
  146. KernelVersion: "3.16.0-0.bpo.4-amd64",
  147. OSImage: "Debian GNU/Linux 7 (wheezy)",
  148. OperatingSystem: goruntime.GOOS,
  149. Architecture: goruntime.GOARCH,
  150. ContainerRuntimeVersion: "test://1.5.0",
  151. KubeletVersion: version.Get().String(),
  152. KubeProxyVersion: version.Get().String(),
  153. },
  154. Capacity: api.ResourceList{
  155. api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
  156. api.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI),
  157. api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
  158. api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
  159. },
  160. Allocatable: api.ResourceList{
  161. api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
  162. api.ResourceMemory: *resource.NewQuantity(9900E6, resource.BinarySI),
  163. api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
  164. api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
  165. },
  166. Addresses: []api.NodeAddress{
  167. {Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
  168. {Type: api.NodeInternalIP, Address: "127.0.0.1"},
  169. },
  170. Images: expectedImageList,
  171. },
  172. }
  173. kubelet.updateRuntimeUp()
  174. if err := kubelet.updateNodeStatus(); err != nil {
  175. t.Errorf("unexpected error: %v", err)
  176. }
  177. actions := kubeClient.Actions()
  178. if len(actions) != 2 {
  179. t.Fatalf("unexpected actions: %v", actions)
  180. }
  181. if !actions[1].Matches("update", "nodes") || actions[1].GetSubresource() != "status" {
  182. t.Fatalf("unexpected actions: %v", actions)
  183. }
  184. updatedNode, ok := actions[1].(core.UpdateAction).GetObject().(*api.Node)
  185. if !ok {
  186. t.Errorf("unexpected object type")
  187. }
  188. for i, cond := range updatedNode.Status.Conditions {
  189. if cond.LastHeartbeatTime.IsZero() {
  190. t.Errorf("unexpected zero last probe timestamp for %v condition", cond.Type)
  191. }
  192. if cond.LastTransitionTime.IsZero() {
  193. t.Errorf("unexpected zero last transition timestamp for %v condition", cond.Type)
  194. }
  195. updatedNode.Status.Conditions[i].LastHeartbeatTime = unversioned.Time{}
  196. updatedNode.Status.Conditions[i].LastTransitionTime = unversioned.Time{}
  197. }
  198. // Version skew workaround. See: https://github.com/kubernetes/kubernetes/issues/16961
  199. if updatedNode.Status.Conditions[len(updatedNode.Status.Conditions)-1].Type != api.NodeReady {
  200. t.Errorf("unexpected node condition order. NodeReady should be last.")
  201. }
  202. if maxImagesInNodeStatus != len(updatedNode.Status.Images) {
  203. t.Errorf("unexpected image list length in node status, expected: %v, got: %v", maxImagesInNodeStatus, len(updatedNode.Status.Images))
  204. } else {
  205. if !api.Semantic.DeepEqual(expectedNode, updatedNode) {
  206. t.Errorf("unexpected objects: %s", diff.ObjectDiff(expectedNode, updatedNode))
  207. }
  208. }
  209. }
  210. func TestUpdateNewNodeOutOfDiskStatusWithTransitionFrequency(t *testing.T) {
  211. testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
  212. kubelet := testKubelet.kubelet
  213. kubeClient := testKubelet.fakeKubeClient
  214. kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{
  215. {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}},
  216. }}).ReactionChain
  217. machineInfo := &cadvisorapi.MachineInfo{
  218. MachineID: "123",
  219. SystemUUID: "abc",
  220. BootID: "1b3",
  221. NumCores: 2,
  222. MemoryCapacity: 1024,
  223. }
  224. mockCadvisor := testKubelet.fakeCadvisor
  225. mockCadvisor.On("Start").Return(nil)
  226. mockCadvisor.On("MachineInfo").Return(machineInfo, nil)
  227. versionInfo := &cadvisorapi.VersionInfo{
  228. KernelVersion: "3.16.0-0.bpo.4-amd64",
  229. ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)",
  230. }
  231. mockCadvisor.On("VersionInfo").Return(versionInfo, nil)
  232. // Make Kubelet report that it has sufficient disk space.
  233. if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, 200, 200, 100, 100); err != nil {
  234. t.Fatalf("can't update disk space manager: %v", err)
  235. }
  236. kubelet.outOfDiskTransitionFrequency = 10 * time.Second
  237. expectedNodeOutOfDiskCondition := api.NodeCondition{
  238. Type: api.NodeOutOfDisk,
  239. Status: api.ConditionFalse,
  240. Reason: "KubeletHasSufficientDisk",
  241. Message: fmt.Sprintf("kubelet has sufficient disk space available"),
  242. LastHeartbeatTime: unversioned.Time{},
  243. LastTransitionTime: unversioned.Time{},
  244. }
  245. kubelet.updateRuntimeUp()
  246. if err := kubelet.updateNodeStatus(); err != nil {
  247. t.Errorf("unexpected error: %v", err)
  248. }
  249. actions := kubeClient.Actions()
  250. if len(actions) != 2 {
  251. t.Fatalf("unexpected actions: %v", actions)
  252. }
  253. if !actions[1].Matches("update", "nodes") || actions[1].GetSubresource() != "status" {
  254. t.Fatalf("unexpected actions: %v", actions)
  255. }
  256. updatedNode, ok := actions[1].(core.UpdateAction).GetObject().(*api.Node)
  257. if !ok {
  258. t.Errorf("unexpected object type")
  259. }
  260. var oodCondition api.NodeCondition
  261. for i, cond := range updatedNode.Status.Conditions {
  262. if cond.LastHeartbeatTime.IsZero() {
  263. t.Errorf("unexpected zero last probe timestamp for %v condition", cond.Type)
  264. }
  265. if cond.LastTransitionTime.IsZero() {
  266. t.Errorf("unexpected zero last transition timestamp for %v condition", cond.Type)
  267. }
  268. updatedNode.Status.Conditions[i].LastHeartbeatTime = unversioned.Time{}
  269. updatedNode.Status.Conditions[i].LastTransitionTime = unversioned.Time{}
  270. if cond.Type == api.NodeOutOfDisk {
  271. oodCondition = updatedNode.Status.Conditions[i]
  272. }
  273. }
  274. if !reflect.DeepEqual(expectedNodeOutOfDiskCondition, oodCondition) {
  275. t.Errorf("unexpected objects: %s", diff.ObjectDiff(expectedNodeOutOfDiskCondition, oodCondition))
  276. }
  277. }
  278. func TestUpdateExistingNodeStatus(t *testing.T) {
  279. testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
  280. kubelet := testKubelet.kubelet
  281. kubeClient := testKubelet.fakeKubeClient
  282. kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{
  283. {
  284. ObjectMeta: api.ObjectMeta{Name: testKubeletHostname},
  285. Spec: api.NodeSpec{},
  286. Status: api.NodeStatus{
  287. Conditions: []api.NodeCondition{
  288. {
  289. Type: api.NodeOutOfDisk,
  290. Status: api.ConditionTrue,
  291. Reason: "KubeletOutOfDisk",
  292. Message: "out of disk space",
  293. LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
  294. LastTransitionTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
  295. },
  296. {
  297. Type: api.NodeMemoryPressure,
  298. Status: api.ConditionFalse,
  299. Reason: "KubeletHasSufficientMemory",
  300. Message: fmt.Sprintf("kubelet has sufficient memory available"),
  301. LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
  302. LastTransitionTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
  303. },
  304. {
  305. Type: api.NodeDiskPressure,
  306. Status: api.ConditionFalse,
  307. Reason: "KubeletHasSufficientDisk",
  308. Message: fmt.Sprintf("kubelet has sufficient disk space available"),
  309. LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
  310. LastTransitionTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
  311. },
  312. {
  313. Type: api.NodeReady,
  314. Status: api.ConditionTrue,
  315. Reason: "KubeletReady",
  316. Message: fmt.Sprintf("kubelet is posting ready status"),
  317. LastHeartbeatTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
  318. LastTransitionTime: unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
  319. },
  320. },
  321. Capacity: api.ResourceList{
  322. api.ResourceCPU: *resource.NewMilliQuantity(3000, resource.DecimalSI),
  323. api.ResourceMemory: *resource.NewQuantity(20E9, resource.BinarySI),
  324. api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
  325. },
  326. Allocatable: api.ResourceList{
  327. api.ResourceCPU: *resource.NewMilliQuantity(2800, resource.DecimalSI),
  328. api.ResourceMemory: *resource.NewQuantity(19900E6, resource.BinarySI),
  329. api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
  330. },
  331. },
  332. },
  333. }}).ReactionChain
  334. mockCadvisor := testKubelet.fakeCadvisor
  335. mockCadvisor.On("Start").Return(nil)
  336. machineInfo := &cadvisorapi.MachineInfo{
  337. MachineID: "123",
  338. SystemUUID: "abc",
  339. BootID: "1b3",
  340. NumCores: 2,
  341. MemoryCapacity: 20E9,
  342. }
  343. mockCadvisor.On("MachineInfo").Return(machineInfo, nil)
  344. versionInfo := &cadvisorapi.VersionInfo{
  345. KernelVersion: "3.16.0-0.bpo.4-amd64",
  346. ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)",
  347. }
  348. mockCadvisor.On("VersionInfo").Return(versionInfo, nil)
  349. // Make kubelet report that it is out of disk space.
  350. if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, 50, 50, 100, 100); err != nil {
  351. t.Fatalf("can't update disk space manager: %v", err)
  352. }
  353. expectedNode := &api.Node{
  354. ObjectMeta: api.ObjectMeta{Name: testKubeletHostname},
  355. Spec: api.NodeSpec{},
  356. Status: api.NodeStatus{
  357. Conditions: []api.NodeCondition{
  358. {
  359. Type: api.NodeOutOfDisk,
  360. Status: api.ConditionTrue,
  361. Reason: "KubeletOutOfDisk",
  362. Message: "out of disk space",
  363. LastHeartbeatTime: unversioned.Time{}, // placeholder
  364. LastTransitionTime: unversioned.Time{}, // placeholder
  365. },
  366. {
  367. Type: api.NodeMemoryPressure,
  368. Status: api.ConditionFalse,
  369. Reason: "KubeletHasSufficientMemory",
  370. Message: fmt.Sprintf("kubelet has sufficient memory available"),
  371. LastHeartbeatTime: unversioned.Time{},
  372. LastTransitionTime: unversioned.Time{},
  373. },
  374. {
  375. Type: api.NodeDiskPressure,
  376. Status: api.ConditionFalse,
  377. Reason: "KubeletHasSufficientDisk",
  378. Message: fmt.Sprintf("kubelet has sufficient disk space available"),
  379. LastHeartbeatTime: unversioned.Time{},
  380. LastTransitionTime: unversioned.Time{},
  381. },
  382. {
  383. Type: api.NodeReady,
  384. Status: api.ConditionTrue,
  385. Reason: "KubeletReady",
  386. Message: fmt.Sprintf("kubelet is posting ready status"),
  387. LastHeartbeatTime: unversioned.Time{}, // placeholder
  388. LastTransitionTime: unversioned.Time{}, // placeholder
  389. },
  390. },
  391. NodeInfo: api.NodeSystemInfo{
  392. MachineID: "123",
  393. SystemUUID: "abc",
  394. BootID: "1b3",
  395. KernelVersion: "3.16.0-0.bpo.4-amd64",
  396. OSImage: "Debian GNU/Linux 7 (wheezy)",
  397. OperatingSystem: goruntime.GOOS,
  398. Architecture: goruntime.GOARCH,
  399. ContainerRuntimeVersion: "test://1.5.0",
  400. KubeletVersion: version.Get().String(),
  401. KubeProxyVersion: version.Get().String(),
  402. },
  403. Capacity: api.ResourceList{
  404. api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
  405. api.ResourceMemory: *resource.NewQuantity(20E9, resource.BinarySI),
  406. api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
  407. api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
  408. },
  409. Allocatable: api.ResourceList{
  410. api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
  411. api.ResourceMemory: *resource.NewQuantity(19900E6, resource.BinarySI),
  412. api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
  413. api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
  414. },
  415. Addresses: []api.NodeAddress{
  416. {Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
  417. {Type: api.NodeInternalIP, Address: "127.0.0.1"},
  418. },
  419. // images will be sorted from max to min in node status.
  420. Images: []api.ContainerImage{
  421. {
  422. Names: []string{"gcr.io/google_containers:v3", "gcr.io/google_containers:v4"},
  423. SizeBytes: 456,
  424. },
  425. {
  426. Names: []string{"gcr.io/google_containers:v1", "gcr.io/google_containers:v2"},
  427. SizeBytes: 123,
  428. },
  429. },
  430. },
  431. }
  432. kubelet.updateRuntimeUp()
  433. if err := kubelet.updateNodeStatus(); err != nil {
  434. t.Errorf("unexpected error: %v", err)
  435. }
  436. actions := kubeClient.Actions()
  437. if len(actions) != 2 {
  438. t.Errorf("unexpected actions: %v", actions)
  439. }
  440. updateAction, ok := actions[1].(core.UpdateAction)
  441. if !ok {
  442. t.Errorf("unexpected action type. expected UpdateAction, got %#v", actions[1])
  443. }
  444. updatedNode, ok := updateAction.GetObject().(*api.Node)
  445. if !ok {
  446. t.Errorf("unexpected object type")
  447. }
  448. for i, cond := range updatedNode.Status.Conditions {
  449. // Expect LastProbeTime to be updated to Now, while LastTransitionTime to be the same.
  450. if old := unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC).Time; reflect.DeepEqual(cond.LastHeartbeatTime.Rfc3339Copy().UTC(), old) {
  451. t.Errorf("Condition %v LastProbeTime: expected \n%v\n, got \n%v", cond.Type, unversioned.Now(), old)
  452. }
  453. if got, want := cond.LastTransitionTime.Rfc3339Copy().UTC(), unversioned.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC).Time; !reflect.DeepEqual(got, want) {
  454. t.Errorf("Condition %v LastTransitionTime: expected \n%#v\n, got \n%#v", cond.Type, want, got)
  455. }
  456. updatedNode.Status.Conditions[i].LastHeartbeatTime = unversioned.Time{}
  457. updatedNode.Status.Conditions[i].LastTransitionTime = unversioned.Time{}
  458. }
  459. // Version skew workaround. See: https://github.com/kubernetes/kubernetes/issues/16961
  460. if updatedNode.Status.Conditions[len(updatedNode.Status.Conditions)-1].Type != api.NodeReady {
  461. t.Errorf("unexpected node condition order. NodeReady should be last.")
  462. }
  463. if !api.Semantic.DeepEqual(expectedNode, updatedNode) {
  464. t.Errorf("unexpected objects: %s", diff.ObjectDiff(expectedNode, updatedNode))
  465. }
  466. }
  467. func TestUpdateExistingNodeOutOfDiskStatusWithTransitionFrequency(t *testing.T) {
  468. testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
  469. kubelet := testKubelet.kubelet
  470. clock := testKubelet.fakeClock
  471. kubeClient := testKubelet.fakeKubeClient
  472. kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{
  473. {
  474. ObjectMeta: api.ObjectMeta{Name: testKubeletHostname},
  475. Spec: api.NodeSpec{},
  476. Status: api.NodeStatus{
  477. Conditions: []api.NodeCondition{
  478. {
  479. Type: api.NodeReady,
  480. Status: api.ConditionTrue,
  481. Reason: "KubeletReady",
  482. Message: fmt.Sprintf("kubelet is posting ready status"),
  483. LastHeartbeatTime: unversioned.NewTime(clock.Now()),
  484. LastTransitionTime: unversioned.NewTime(clock.Now()),
  485. },
  486. {
  487. Type: api.NodeOutOfDisk,
  488. Status: api.ConditionTrue,
  489. Reason: "KubeletOutOfDisk",
  490. Message: "out of disk space",
  491. LastHeartbeatTime: unversioned.NewTime(clock.Now()),
  492. LastTransitionTime: unversioned.NewTime(clock.Now()),
  493. },
  494. },
  495. },
  496. },
  497. }}).ReactionChain
  498. mockCadvisor := testKubelet.fakeCadvisor
  499. machineInfo := &cadvisorapi.MachineInfo{
  500. MachineID: "123",
  501. SystemUUID: "abc",
  502. BootID: "1b3",
  503. NumCores: 2,
  504. MemoryCapacity: 1024,
  505. }
  506. fsInfo := cadvisorapiv2.FsInfo{
  507. Device: "123",
  508. }
  509. mockCadvisor.On("Start").Return(nil)
  510. mockCadvisor.On("MachineInfo").Return(machineInfo, nil)
  511. mockCadvisor.On("ImagesFsInfo").Return(fsInfo, nil)
  512. mockCadvisor.On("RootFsInfo").Return(fsInfo, nil)
  513. versionInfo := &cadvisorapi.VersionInfo{
  514. KernelVersion: "3.16.0-0.bpo.4-amd64",
  515. ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)",
  516. DockerVersion: "1.5.0",
  517. }
  518. mockCadvisor.On("VersionInfo").Return(versionInfo, nil)
  519. kubelet.outOfDiskTransitionFrequency = 5 * time.Second
  520. ood := api.NodeCondition{
  521. Type: api.NodeOutOfDisk,
  522. Status: api.ConditionTrue,
  523. Reason: "KubeletOutOfDisk",
  524. Message: "out of disk space",
  525. LastHeartbeatTime: unversioned.NewTime(clock.Now()), // placeholder
  526. LastTransitionTime: unversioned.NewTime(clock.Now()), // placeholder
  527. }
  528. noOod := api.NodeCondition{
  529. Type: api.NodeOutOfDisk,
  530. Status: api.ConditionFalse,
  531. Reason: "KubeletHasSufficientDisk",
  532. Message: fmt.Sprintf("kubelet has sufficient disk space available"),
  533. LastHeartbeatTime: unversioned.NewTime(clock.Now()), // placeholder
  534. LastTransitionTime: unversioned.NewTime(clock.Now()), // placeholder
  535. }
  536. testCases := []struct {
  537. rootFsAvail uint64
  538. dockerFsAvail uint64
  539. expected api.NodeCondition
  540. }{
  541. {
  542. // NodeOutOfDisk==false
  543. rootFsAvail: 200,
  544. dockerFsAvail: 200,
  545. expected: ood,
  546. },
  547. {
  548. // NodeOutOfDisk==true
  549. rootFsAvail: 50,
  550. dockerFsAvail: 200,
  551. expected: ood,
  552. },
  553. {
  554. // NodeOutOfDisk==false
  555. rootFsAvail: 200,
  556. dockerFsAvail: 200,
  557. expected: ood,
  558. },
  559. {
  560. // NodeOutOfDisk==true
  561. rootFsAvail: 200,
  562. dockerFsAvail: 50,
  563. expected: ood,
  564. },
  565. {
  566. // NodeOutOfDisk==false
  567. rootFsAvail: 200,
  568. dockerFsAvail: 200,
  569. expected: noOod,
  570. },
  571. }
  572. kubelet.updateRuntimeUp()
  573. for tcIdx, tc := range testCases {
  574. // Step by a second
  575. clock.Step(1 * time.Second)
  576. // Setup expected times.
  577. tc.expected.LastHeartbeatTime = unversioned.NewTime(clock.Now())
  578. // In the last case, there should be a status transition for NodeOutOfDisk
  579. if tcIdx == len(testCases)-1 {
  580. tc.expected.LastTransitionTime = unversioned.NewTime(clock.Now())
  581. }
  582. // Make kubelet report that it has sufficient disk space
  583. if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, tc.rootFsAvail, tc.dockerFsAvail, 100, 100); err != nil {
  584. t.Fatalf("can't update disk space manager: %v", err)
  585. }
  586. if err := kubelet.updateNodeStatus(); err != nil {
  587. t.Errorf("unexpected error: %v", err)
  588. }
  589. actions := kubeClient.Actions()
  590. if len(actions) != 2 {
  591. t.Errorf("%d. unexpected actions: %v", tcIdx, actions)
  592. }
  593. updateAction, ok := actions[1].(core.UpdateAction)
  594. if !ok {
  595. t.Errorf("%d. unexpected action type. expected UpdateAction, got %#v", tcIdx, actions[1])
  596. }
  597. updatedNode, ok := updateAction.GetObject().(*api.Node)
  598. if !ok {
  599. t.Errorf("%d. unexpected object type", tcIdx)
  600. }
  601. kubeClient.ClearActions()
  602. var oodCondition api.NodeCondition
  603. for i, cond := range updatedNode.Status.Conditions {
  604. if cond.Type == api.NodeOutOfDisk {
  605. oodCondition = updatedNode.Status.Conditions[i]
  606. }
  607. }
  608. if !reflect.DeepEqual(tc.expected, oodCondition) {
  609. t.Errorf("%d.\nunexpected objects: %s", tcIdx, diff.ObjectDiff(tc.expected, oodCondition))
  610. }
  611. }
  612. }
  613. func TestUpdateNodeStatusWithRuntimeStateError(t *testing.T) {
  614. testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
  615. kubelet := testKubelet.kubelet
  616. clock := testKubelet.fakeClock
  617. kubeClient := testKubelet.fakeKubeClient
  618. kubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{
  619. {ObjectMeta: api.ObjectMeta{Name: testKubeletHostname}},
  620. }}).ReactionChain
  621. mockCadvisor := testKubelet.fakeCadvisor
  622. mockCadvisor.On("Start").Return(nil)
  623. machineInfo := &cadvisorapi.MachineInfo{
  624. MachineID: "123",
  625. SystemUUID: "abc",
  626. BootID: "1b3",
  627. NumCores: 2,
  628. MemoryCapacity: 10E9,
  629. }
  630. mockCadvisor.On("MachineInfo").Return(machineInfo, nil)
  631. versionInfo := &cadvisorapi.VersionInfo{
  632. KernelVersion: "3.16.0-0.bpo.4-amd64",
  633. ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)",
  634. }
  635. mockCadvisor.On("VersionInfo").Return(versionInfo, nil)
  636. // Make kubelet report that it has sufficient disk space.
  637. if err := updateDiskSpacePolicy(kubelet, mockCadvisor, 500, 500, 200, 200, 100, 100); err != nil {
  638. t.Fatalf("can't update disk space manager: %v", err)
  639. }
  640. expectedNode := &api.Node{
  641. ObjectMeta: api.ObjectMeta{Name: testKubeletHostname},
  642. Spec: api.NodeSpec{},
  643. Status: api.NodeStatus{
  644. Conditions: []api.NodeCondition{
  645. {
  646. Type: api.NodeOutOfDisk,
  647. Status: api.ConditionFalse,
  648. Reason: "KubeletHasSufficientDisk",
  649. Message: "kubelet has sufficient disk space available",
  650. LastHeartbeatTime: unversioned.Time{},
  651. LastTransitionTime: unversioned.Time{},
  652. },
  653. {
  654. Type: api.NodeMemoryPressure,
  655. Status: api.ConditionFalse,
  656. Reason: "KubeletHasSufficientMemory",
  657. Message: fmt.Sprintf("kubelet has sufficient memory available"),
  658. LastHeartbeatTime: unversioned.Time{},
  659. LastTransitionTime: unversioned.Time{},
  660. },
  661. {
  662. Type: api.NodeDiskPressure,
  663. Status: api.ConditionFalse,
  664. Reason: "KubeletHasNoDiskPressure",
  665. Message: fmt.Sprintf("kubelet has no disk pressure"),
  666. LastHeartbeatTime: unversioned.Time{},
  667. LastTransitionTime: unversioned.Time{},
  668. },
  669. {}, //placeholder
  670. },
  671. NodeInfo: api.NodeSystemInfo{
  672. MachineID: "123",
  673. SystemUUID: "abc",
  674. BootID: "1b3",
  675. KernelVersion: "3.16.0-0.bpo.4-amd64",
  676. OSImage: "Debian GNU/Linux 7 (wheezy)",
  677. OperatingSystem: goruntime.GOOS,
  678. Architecture: goruntime.GOARCH,
  679. ContainerRuntimeVersion: "test://1.5.0",
  680. KubeletVersion: version.Get().String(),
  681. KubeProxyVersion: version.Get().String(),
  682. },
  683. Capacity: api.ResourceList{
  684. api.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
  685. api.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI),
  686. api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
  687. api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
  688. },
  689. Allocatable: api.ResourceList{
  690. api.ResourceCPU: *resource.NewMilliQuantity(1800, resource.DecimalSI),
  691. api.ResourceMemory: *resource.NewQuantity(9900E6, resource.BinarySI),
  692. api.ResourcePods: *resource.NewQuantity(0, resource.DecimalSI),
  693. api.ResourceNvidiaGPU: *resource.NewQuantity(0, resource.DecimalSI),
  694. },
  695. Addresses: []api.NodeAddress{
  696. {Type: api.NodeLegacyHostIP, Address: "127.0.0.1"},
  697. {Type: api.NodeInternalIP, Address: "127.0.0.1"},
  698. },
  699. Images: []api.ContainerImage{
  700. {
  701. Names: []string{"gcr.io/google_containers:v3", "gcr.io/google_containers:v4"},
  702. SizeBytes: 456,
  703. },
  704. {
  705. Names: []string{"gcr.io/google_containers:v1", "gcr.io/google_containers:v2"},
  706. SizeBytes: 123,
  707. },
  708. },
  709. },
  710. }
  711. checkNodeStatus := func(status api.ConditionStatus, reason, message string) {
  712. kubeClient.ClearActions()
  713. if err := kubelet.updateNodeStatus(); err != nil {
  714. t.Errorf("unexpected error: %v", err)
  715. }
  716. actions := kubeClient.Actions()
  717. if len(actions) != 2 {
  718. t.Fatalf("unexpected actions: %v", actions)
  719. }
  720. if !actions[1].Matches("update", "nodes") || actions[1].GetSubresource() != "status" {
  721. t.Fatalf("unexpected actions: %v", actions)
  722. }
  723. updatedNode, ok := actions[1].(core.UpdateAction).GetObject().(*api.Node)
  724. if !ok {
  725. t.Errorf("unexpected action type. expected UpdateAction, got %#v", actions[1])
  726. }
  727. for i, cond := range updatedNode.Status.Conditions {
  728. if cond.LastHeartbeatTime.IsZero() {
  729. t.Errorf("unexpected zero last probe timestamp")
  730. }
  731. if cond.LastTransitionTime.IsZero() {
  732. t.Errorf("unexpected zero last transition timestamp")
  733. }
  734. updatedNode.Status.Conditions[i].LastHeartbeatTime = unversioned.Time{}
  735. updatedNode.Status.Conditions[i].LastTransitionTime = unversioned.Time{}
  736. }
  737. // Version skew workaround. See: https://github.com/kubernetes/kubernetes/issues/16961
  738. lastIndex := len(updatedNode.Status.Conditions) - 1
  739. if updatedNode.Status.Conditions[lastIndex].Type != api.NodeReady {
  740. t.Errorf("unexpected node condition order. NodeReady should be last.")
  741. }
  742. expectedNode.Status.Conditions[lastIndex] = api.NodeCondition{
  743. Type: api.NodeReady,
  744. Status: status,
  745. Reason: reason,
  746. Message: message,
  747. LastHeartbeatTime: unversioned.Time{},
  748. LastTransitionTime: unversioned.Time{},
  749. }
  750. if !api.Semantic.DeepEqual(expectedNode, updatedNode) {
  751. t.Errorf("unexpected objects: %s", diff.ObjectDiff(expectedNode, updatedNode))
  752. }
  753. }
  754. readyMessage := "kubelet is posting ready status"
  755. downMessage := "container runtime is down"
  756. // Should report kubelet not ready if the runtime check is out of date
  757. clock.SetTime(time.Now().Add(-maxWaitForContainerRuntime))
  758. kubelet.updateRuntimeUp()
  759. checkNodeStatus(api.ConditionFalse, "KubeletNotReady", downMessage)
  760. // Should report kubelet ready if the runtime check is updated
  761. clock.SetTime(time.Now())
  762. kubelet.updateRuntimeUp()
  763. checkNodeStatus(api.ConditionTrue, "KubeletReady", readyMessage)
  764. // Should report kubelet not ready if the runtime check is out of date
  765. clock.SetTime(time.Now().Add(-maxWaitForContainerRuntime))
  766. kubelet.updateRuntimeUp()
  767. checkNodeStatus(api.ConditionFalse, "KubeletNotReady", downMessage)
  768. // Should report kubelet not ready if the runtime check failed
  769. fakeRuntime := testKubelet.fakeRuntime
  770. // Inject error into fake runtime status check, node should be NotReady
  771. fakeRuntime.StatusErr = fmt.Errorf("injected runtime status error")
  772. clock.SetTime(time.Now())
  773. kubelet.updateRuntimeUp()
  774. checkNodeStatus(api.ConditionFalse, "KubeletNotReady", downMessage)
  775. }
  776. func TestUpdateNodeStatusError(t *testing.T) {
  777. testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
  778. kubelet := testKubelet.kubelet
  779. // No matching node for the kubelet
  780. testKubelet.fakeKubeClient.ReactionChain = fake.NewSimpleClientset(&api.NodeList{Items: []api.Node{}}).ReactionChain
  781. if err := kubelet.updateNodeStatus(); err == nil {
  782. t.Errorf("unexpected non error: %v", err)
  783. }
  784. if len(testKubelet.fakeKubeClient.Actions()) != nodeStatusUpdateRetry {
  785. t.Errorf("unexpected actions: %v", testKubelet.fakeKubeClient.Actions())
  786. }
  787. }
  788. func TestRegisterWithApiServer(t *testing.T) {
  789. testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */)
  790. kubelet := testKubelet.kubelet
  791. kubeClient := testKubelet.fakeKubeClient
  792. kubeClient.AddReactor("create", "nodes", func(action core.Action) (bool, runtime.Object, error) {
  793. // Return an error on create.
  794. return true, &api.Node{}, &apierrors.StatusError{
  795. ErrStatus: unversioned.Status{Reason: unversioned.StatusReasonAlreadyExists},
  796. }
  797. })
  798. kubeClient.AddReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) {
  799. // Return an existing (matching) node on get.
  800. return true, &api.Node{
  801. ObjectMeta: api.ObjectMeta{Name: testKubeletHostname},
  802. Spec: api.NodeSpec{ExternalID: testKubeletHostname},
  803. }, nil
  804. })
  805. kubeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) {
  806. return true, nil, fmt.Errorf("no reaction implemented for %s", action)
  807. })
  808. machineInfo := &cadvisorapi.MachineInfo{
  809. MachineID: "123",
  810. SystemUUID: "abc",
  811. BootID: "1b3",
  812. NumCores: 2,
  813. MemoryCapacity: 1024,
  814. }
  815. mockCadvisor := testKubelet.fakeCadvisor
  816. mockCadvisor.On("MachineInfo").Return(machineInfo, nil)
  817. versionInfo := &cadvisorapi.VersionInfo{
  818. KernelVersion: "3.16.0-0.bpo.4-amd64",
  819. ContainerOsVersion: "Debian GNU/Linux 7 (wheezy)",
  820. DockerVersion: "1.5.0",
  821. }
  822. mockCadvisor.On("VersionInfo").Return(versionInfo, nil)
  823. mockCadvisor.On("ImagesFsInfo").Return(cadvisorapiv2.FsInfo{
  824. Usage: 400 * mb,
  825. Capacity: 1000 * mb,
  826. Available: 600 * mb,
  827. }, nil)
  828. mockCadvisor.On("RootFsInfo").Return(cadvisorapiv2.FsInfo{
  829. Usage: 9 * mb,
  830. Capacity: 10 * mb,
  831. }, nil)
  832. done := make(chan struct{})
  833. go func() {
  834. kubelet.registerWithApiServer()
  835. done <- struct{}{}
  836. }()
  837. select {
  838. case <-time.After(wait.ForeverTestTimeout):
  839. t.Errorf("timed out waiting for registration")
  840. case <-done:
  841. return
  842. }
  843. }
  844. func TestTryRegisterWithApiServer(t *testing.T) {
  845. alreadyExists := &apierrors.StatusError{
  846. ErrStatus: unversioned.Status{Reason: unversioned.StatusReasonAlreadyExists},
  847. }
  848. conflict := &apierrors.StatusError{
  849. ErrStatus: unversioned.Status{Reason: unversioned.StatusReasonConflict},
  850. }
  851. newNode := func(cmad bool, externalID string) *api.Node {
  852. node := &api.Node{
  853. ObjectMeta: api.ObjectMeta{},
  854. Spec: api.NodeSpec{
  855. ExternalID: externalID,
  856. },
  857. }
  858. if cmad {
  859. node.Annotations = make(map[string]string)
  860. node.Annotations[volumehelper.ControllerManagedAttachAnnotation] = "true"
  861. }
  862. return node
  863. }
  864. cases := []struct {
  865. name string
  866. newNode *api.Node
  867. existingNode *api.Node
  868. createError error
  869. getError error
  870. updateError error
  871. deleteError error
  872. expectedResult bool
  873. expectedActions int
  874. testSavedNode bool
  875. savedNodeIndex int
  876. savedNodeCMAD bool
  877. }{
  878. {
  879. name: "success case - new node",
  880. newNode: &api.Node{},
  881. expectedResult: true,
  882. expectedActions: 1,
  883. },
  884. {
  885. name: "success case - existing node - no change in CMAD",
  886. newNode: newNode(true, "a"),
  887. createError: alreadyExists,
  888. existingNode: newNode(true, "a"),
  889. expectedResult: true,
  890. expectedActions: 2,
  891. },
  892. {
  893. name: "success case - existing node - CMAD disabled",
  894. newNode: newNode(false, "a"),
  895. createError: alreadyExists,
  896. existingNode: newNode(true, "a"),
  897. expectedResult: true,
  898. expectedActions: 3,
  899. testSavedNode: true,
  900. savedNodeIndex: 2,
  901. savedNodeCMAD: false,
  902. },
  903. {
  904. name: "success case - existing node - CMAD enabled",
  905. newNode: newNode(true, "a"),
  906. createError: alreadyExists,
  907. existingNode: newNode(false, "a"),
  908. expectedResult: true,
  909. expectedActions: 3,
  910. testSavedNode: true,
  911. savedNodeIndex: 2,
  912. savedNodeCMAD: true,
  913. },
  914. {
  915. name: "success case - external ID changed",
  916. newNode: newNode(false, "b"),
  917. createError: alreadyExists,
  918. existingNode: newNode(false, "a"),
  919. expectedResult: false,
  920. expectedActions: 3,
  921. },
  922. {
  923. name: "create failed",
  924. newNode: newNode(false, "b"),
  925. createError: conflict,
  926. expectedResult: false,
  927. expectedActions: 1,
  928. },
  929. {
  930. name: "get existing node failed",
  931. newNode: newNode(false, "a"),
  932. createError: alreadyExists,
  933. getError: conflict,
  934. expectedResult: false,
  935. expectedActions: 2,
  936. },
  937. {
  938. name: "update existing node failed",
  939. newNode: newNode(false, "a"),
  940. createError: alreadyExists,
  941. existingNode: newNode(true, "a"),
  942. updateError: conflict,
  943. expectedResult: false,
  944. expectedActions: 3,
  945. },
  946. {
  947. name: "delete existing node failed",
  948. newNode: newNode(false, "b"),
  949. createError: alreadyExists,
  950. existingNode: newNode(false, "a"),
  951. deleteError: conflict,
  952. expectedResult: false,
  953. expectedActions: 3,
  954. },
  955. }
  956. for _, tc := range cases {
  957. testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled is a don't-care for this test */)
  958. kubelet := testKubelet.kubelet
  959. kubeClient := testKubelet.fakeKubeClient
  960. kubeClient.AddReactor("create", "nodes", func(action core.Action) (bool, runtime.Object, error) {
  961. return true, nil, tc.createError
  962. })
  963. kubeClient.AddReactor("get", "nodes", func(action core.Action) (bool, runtime.Object, error) {
  964. // Return an existing (matching) node on get.
  965. return true, tc.existingNode, tc.getError
  966. })
  967. kubeClient.AddReactor("update", "nodes", func(action core.Action) (bool, runtime.Object, error) {
  968. return true, nil, tc.updateError
  969. })
  970. kubeClient.AddReactor("delete", "nodes", func(action core.Action) (bool, runtime.Object, error) {
  971. return true, nil, tc.deleteError
  972. })
  973. kubeClient.AddReactor("*", "*", func(action core.Action) (bool, runtime.Object, error) {
  974. return true, nil, fmt.Errorf("no reaction implemented for %s", action)
  975. })
  976. result := kubelet.tryRegisterWithApiServer(tc.newNode)
  977. if e, a := tc.expectedResult, result; e != a {
  978. t.Errorf("%v: unexpected result; expected %v got %v", tc.name, e, a)
  979. continue
  980. }
  981. actions := kubeClient.Actions()
  982. if e, a := tc.expectedActions, len(actions); e != a {
  983. t.Errorf("%v: unexpected number of actions, expected %v, got %v", tc.name, e, a)
  984. }
  985. if tc.testSavedNode {
  986. var savedNode *api.Node
  987. var ok bool
  988. t.Logf("actions: %v: %+v", len(actions), actions)
  989. action := actions[tc.savedNodeIndex]
  990. if action.GetVerb() == "create" {
  991. createAction := action.(core.CreateAction)
  992. savedNode, ok = createAction.GetObject().(*api.Node)
  993. if !ok {
  994. t.Errorf("%v: unexpected type; couldn't convert to *api.Node: %+v", tc.name, createAction.GetObject())
  995. continue
  996. }
  997. } else if action.GetVerb() == "update" {
  998. updateAction := action.(core.UpdateAction)
  999. savedNode, ok = updateAction.GetObject().(*api.Node)
  1000. if !ok {
  1001. t.Errorf("%v: unexpected type; couldn't convert to *api.Node: %+v", tc.name, updateAction.GetObject())
  1002. continue
  1003. }
  1004. }
  1005. actualCMAD, _ := strconv.ParseBool(savedNode.Annotations[volumehelper.ControllerManagedAttachAnnotation])
  1006. if e, a := tc.savedNodeCMAD, actualCMAD; e != a {
  1007. t.Errorf("%v: unexpected attach-detach value on saved node; expected %v got %v", tc.name, e, a)
  1008. }
  1009. }
  1010. }
  1011. }