dynamic_kubelet_config_test.go 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  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 e2e_node
  14. import (
  15. "fmt"
  16. "reflect"
  17. "strings"
  18. "time"
  19. "github.com/davecgh/go-spew/spew"
  20. apiv1 "k8s.io/api/core/v1"
  21. apiequality "k8s.io/apimachinery/pkg/api/equality"
  22. apierrors "k8s.io/apimachinery/pkg/api/errors"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/util/sets"
  25. kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
  26. controller "k8s.io/kubernetes/pkg/kubelet/kubeletconfig"
  27. "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status"
  28. "k8s.io/kubernetes/pkg/kubelet/metrics"
  29. frameworkmetrics "k8s.io/kubernetes/test/e2e/framework/metrics"
  30. "k8s.io/kubernetes/test/e2e/framework"
  31. "github.com/prometheus/common/model"
  32. . "github.com/onsi/ginkgo"
  33. . "github.com/onsi/gomega"
  34. )
  35. const itDescription = "status and events should match expectations"
  36. type expectNodeConfigStatus struct {
  37. lastKnownGood *apiv1.NodeConfigSource
  38. err string
  39. // If true, expect Status.Config.Active == Status.Config.LastKnownGood,
  40. // otherwise expect Status.Config.Active == Status.Config.Assigned.
  41. lkgActive bool
  42. }
  43. type nodeConfigTestCase struct {
  44. desc string
  45. configSource *apiv1.NodeConfigSource
  46. configMap *apiv1.ConfigMap
  47. expectConfigStatus expectNodeConfigStatus
  48. expectConfig *kubeletconfig.KubeletConfiguration
  49. // whether to expect this substring in an error returned from the API server when updating the config source
  50. apierr string
  51. // whether the state would cause a config change event as a result of the update to Node.Spec.ConfigSource,
  52. // assuming that the current source would have also caused a config change event.
  53. // for example, some malformed references may result in a download failure, in which case the Kubelet
  54. // does not restart to change config, while an invalid payload will be detected upon restart
  55. event bool
  56. }
  57. // This test is marked [Disruptive] because the Kubelet restarts several times during this test.
  58. var _ = framework.KubeDescribe("[Feature:DynamicKubeletConfig][NodeFeature:DynamicKubeletConfig][Serial][Disruptive]", func() {
  59. f := framework.NewDefaultFramework("dynamic-kubelet-configuration-test")
  60. var beforeNode *apiv1.Node
  61. var beforeConfigMap *apiv1.ConfigMap
  62. var beforeKC *kubeletconfig.KubeletConfiguration
  63. var localKC *kubeletconfig.KubeletConfiguration
  64. // Dummy context to prevent framework's AfterEach from cleaning up before this test's AfterEach can run
  65. Context("", func() {
  66. BeforeEach(func() {
  67. // make sure Dynamic Kubelet Configuration feature is enabled on the Kubelet we are about to test
  68. enabled, err := isKubeletConfigEnabled(f)
  69. framework.ExpectNoError(err)
  70. if !enabled {
  71. framework.ExpectNoError(fmt.Errorf("The Dynamic Kubelet Configuration feature is not enabled.\n" +
  72. "Pass --feature-gates=DynamicKubeletConfig=true to the Kubelet and API server to enable this feature.\n" +
  73. "For `make test-e2e-node`, you can set `TEST_ARGS='--feature-gates=DynamicKubeletConfig=true'`."))
  74. }
  75. // record before state so we can restore it after the test
  76. if beforeNode == nil {
  77. node, err := f.ClientSet.CoreV1().Nodes().Get(framework.TestContext.NodeName, metav1.GetOptions{})
  78. framework.ExpectNoError(err)
  79. beforeNode = node
  80. }
  81. if source := beforeNode.Spec.ConfigSource; source != nil {
  82. if source.ConfigMap != nil {
  83. cm, err := f.ClientSet.CoreV1().ConfigMaps(source.ConfigMap.Namespace).Get(source.ConfigMap.Name, metav1.GetOptions{})
  84. framework.ExpectNoError(err)
  85. beforeConfigMap = cm
  86. }
  87. }
  88. if beforeKC == nil {
  89. kc, err := getCurrentKubeletConfig()
  90. framework.ExpectNoError(err)
  91. beforeKC = kc
  92. }
  93. // reset the node's assigned/active/last-known-good config by setting the source to nil,
  94. // so each test starts from a clean-slate
  95. (&nodeConfigTestCase{
  96. desc: "reset via nil config source",
  97. configSource: nil,
  98. }).run(f, setConfigSourceFunc, false, 0)
  99. // record local KC so we can check it during tests that roll back to nil last-known-good
  100. if localKC == nil {
  101. kc, err := getCurrentKubeletConfig()
  102. framework.ExpectNoError(err)
  103. localKC = kc
  104. }
  105. })
  106. AfterEach(func() {
  107. // clean-slate the Node again (prevents last-known-good from any tests from leaking through)
  108. (&nodeConfigTestCase{
  109. desc: "reset via nil config source",
  110. configSource: nil,
  111. }).run(f, setConfigSourceFunc, false, 0)
  112. // restore the values from before the test before moving on
  113. restore := &nodeConfigTestCase{
  114. desc: "restore values from before test",
  115. configSource: beforeNode.Spec.ConfigSource,
  116. configMap: beforeConfigMap,
  117. expectConfig: beforeKC,
  118. }
  119. restore.run(f, setConfigSourceFunc, false, 0)
  120. })
  121. Context("update Node.Spec.ConfigSource: state transitions:", func() {
  122. It(itDescription, func() {
  123. var err error
  124. // we base the "correct" configmap off of the configuration from before the test
  125. correctKC := beforeKC.DeepCopy()
  126. correctConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-correct", correctKC)
  127. correctConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(correctConfigMap)
  128. framework.ExpectNoError(err)
  129. // fail to parse, we insert some bogus stuff into the configMap
  130. failParseConfigMap := &apiv1.ConfigMap{
  131. ObjectMeta: metav1.ObjectMeta{Name: "dynamic-kubelet-config-test-fail-parse"},
  132. Data: map[string]string{
  133. "kubelet": "{0xdeadbeef}",
  134. },
  135. }
  136. failParseConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(failParseConfigMap)
  137. framework.ExpectNoError(err)
  138. // fail to validate, we make a copy of correct and set an invalid KubeAPIQPS on kc before serializing
  139. invalidKC := correctKC.DeepCopy()
  140. invalidKC.KubeAPIQPS = -1
  141. failValidateConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-fail-validate", invalidKC)
  142. failValidateConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(failValidateConfigMap)
  143. framework.ExpectNoError(err)
  144. correctSource := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  145. Namespace: correctConfigMap.Namespace,
  146. Name: correctConfigMap.Name,
  147. KubeletConfigKey: "kubelet",
  148. }}
  149. failParseSource := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  150. Namespace: failParseConfigMap.Namespace,
  151. Name: failParseConfigMap.Name,
  152. KubeletConfigKey: "kubelet",
  153. }}
  154. failValidateSource := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  155. Namespace: failValidateConfigMap.Namespace,
  156. Name: failValidateConfigMap.Name,
  157. KubeletConfigKey: "kubelet",
  158. }}
  159. cases := []nodeConfigTestCase{
  160. {
  161. desc: "Node.Spec.ConfigSource is nil",
  162. configSource: nil,
  163. expectConfigStatus: expectNodeConfigStatus{},
  164. expectConfig: nil,
  165. event: true,
  166. },
  167. {
  168. desc: "Node.Spec.ConfigSource has all nil subfields",
  169. configSource: &apiv1.NodeConfigSource{},
  170. apierr: "exactly one reference subfield must be non-nil",
  171. },
  172. {
  173. desc: "Node.Spec.ConfigSource.ConfigMap is missing namespace",
  174. configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  175. Name: "bar",
  176. KubeletConfigKey: "kubelet",
  177. }}, // missing Namespace
  178. apierr: "spec.configSource.configMap.namespace: Required value: namespace must be set",
  179. },
  180. {
  181. desc: "Node.Spec.ConfigSource.ConfigMap is missing name",
  182. configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  183. Namespace: "foo",
  184. KubeletConfigKey: "kubelet",
  185. }}, // missing Name
  186. apierr: "spec.configSource.configMap.name: Required value: name must be set",
  187. },
  188. {
  189. desc: "Node.Spec.ConfigSource.ConfigMap is missing kubeletConfigKey",
  190. configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  191. Namespace: "foo",
  192. Name: "bar",
  193. }}, // missing KubeletConfigKey
  194. apierr: "spec.configSource.configMap.kubeletConfigKey: Required value: kubeletConfigKey must be set",
  195. },
  196. {
  197. desc: "Node.Spec.ConfigSource.ConfigMap.UID is illegally specified",
  198. configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  199. UID: "foo",
  200. Name: "bar",
  201. Namespace: "baz",
  202. KubeletConfigKey: "kubelet",
  203. }},
  204. apierr: "spec.configSource.configMap.uid: Forbidden: uid must not be set in spec",
  205. },
  206. {
  207. desc: "Node.Spec.ConfigSource.ConfigMap.ResourceVersion is illegally specified",
  208. configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  209. Name: "bar",
  210. Namespace: "baz",
  211. ResourceVersion: "1",
  212. KubeletConfigKey: "kubelet",
  213. }},
  214. apierr: "spec.configSource.configMap.resourceVersion: Forbidden: resourceVersion must not be set in spec",
  215. },
  216. {
  217. desc: "Node.Spec.ConfigSource.ConfigMap has invalid namespace",
  218. configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  219. Name: "bar",
  220. Namespace: "../baz",
  221. KubeletConfigKey: "kubelet",
  222. }},
  223. apierr: "spec.configSource.configMap.namespace: Invalid value",
  224. },
  225. {
  226. desc: "Node.Spec.ConfigSource.ConfigMap has invalid name",
  227. configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  228. Name: "../bar",
  229. Namespace: "baz",
  230. KubeletConfigKey: "kubelet",
  231. }},
  232. apierr: "spec.configSource.configMap.name: Invalid value",
  233. },
  234. {
  235. desc: "Node.Spec.ConfigSource.ConfigMap has invalid kubeletConfigKey",
  236. configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  237. Name: "bar",
  238. Namespace: "baz",
  239. KubeletConfigKey: "../qux",
  240. }},
  241. apierr: "spec.configSource.configMap.kubeletConfigKey: Invalid value",
  242. },
  243. {
  244. desc: "correct",
  245. configSource: correctSource,
  246. configMap: correctConfigMap,
  247. expectConfig: correctKC,
  248. event: true,
  249. },
  250. {
  251. desc: "fail-parse",
  252. configSource: failParseSource,
  253. configMap: failParseConfigMap,
  254. expectConfigStatus: expectNodeConfigStatus{
  255. err: status.LoadError,
  256. lkgActive: true,
  257. },
  258. expectConfig: localKC,
  259. event: true,
  260. },
  261. {
  262. desc: "fail-validate",
  263. configSource: failValidateSource,
  264. configMap: failValidateConfigMap,
  265. expectConfigStatus: expectNodeConfigStatus{
  266. err: status.ValidateError,
  267. lkgActive: true,
  268. },
  269. expectConfig: localKC,
  270. event: true,
  271. },
  272. }
  273. L := len(cases)
  274. for i := 1; i <= L; i++ { // need one less iteration than the number of cases
  275. testBothDirections(f, setConfigSourceFunc, &cases[i-1 : i][0], cases[i:L], 0)
  276. }
  277. })
  278. })
  279. Context("update Node.Spec.ConfigSource: recover to last-known-good ConfigMap:", func() {
  280. It(itDescription, func() {
  281. var err error
  282. // we base the "lkg" configmap off of the configuration from before the test
  283. lkgKC := beforeKC.DeepCopy()
  284. lkgConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-intended-lkg", lkgKC)
  285. lkgConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(lkgConfigMap)
  286. framework.ExpectNoError(err)
  287. // bad config map, we insert some bogus stuff into the configMap
  288. badConfigMap := &apiv1.ConfigMap{
  289. ObjectMeta: metav1.ObjectMeta{Name: "dynamic-kubelet-config-test-bad"},
  290. Data: map[string]string{
  291. "kubelet": "{0xdeadbeef}",
  292. },
  293. }
  294. badConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(badConfigMap)
  295. framework.ExpectNoError(err)
  296. lkgSource := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  297. Namespace: lkgConfigMap.Namespace,
  298. Name: lkgConfigMap.Name,
  299. KubeletConfigKey: "kubelet",
  300. }}
  301. lkgStatus := lkgSource.DeepCopy()
  302. lkgStatus.ConfigMap.UID = lkgConfigMap.UID
  303. lkgStatus.ConfigMap.ResourceVersion = lkgConfigMap.ResourceVersion
  304. badSource := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  305. Namespace: badConfigMap.Namespace,
  306. Name: badConfigMap.Name,
  307. KubeletConfigKey: "kubelet",
  308. }}
  309. cases := []nodeConfigTestCase{
  310. {
  311. desc: "intended last-known-good",
  312. configSource: lkgSource,
  313. configMap: lkgConfigMap,
  314. expectConfigStatus: expectNodeConfigStatus{
  315. lastKnownGood: lkgStatus,
  316. },
  317. expectConfig: lkgKC,
  318. event: true,
  319. },
  320. {
  321. desc: "bad config",
  322. configSource: badSource,
  323. configMap: badConfigMap,
  324. expectConfigStatus: expectNodeConfigStatus{
  325. lastKnownGood: lkgStatus,
  326. err: status.LoadError,
  327. lkgActive: true,
  328. },
  329. expectConfig: lkgKC,
  330. event: true,
  331. },
  332. }
  333. // wait 12 minutes after setting the first config to ensure it has time to pass the trial duration
  334. testBothDirections(f, setConfigSourceFunc, &cases[0], cases[1:], 12*time.Minute)
  335. })
  336. })
  337. Context("update Node.Spec.ConfigSource: recover to last-known-good ConfigMap.KubeletConfigKey:", func() {
  338. It(itDescription, func() {
  339. const badConfigKey = "bad"
  340. var err error
  341. // we base the "lkg" configmap off of the configuration from before the test
  342. lkgKC := beforeKC.DeepCopy()
  343. combinedConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-combined", lkgKC)
  344. combinedConfigMap.Data[badConfigKey] = "{0xdeadbeef}"
  345. combinedConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(combinedConfigMap)
  346. framework.ExpectNoError(err)
  347. lkgSource := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  348. Namespace: combinedConfigMap.Namespace,
  349. Name: combinedConfigMap.Name,
  350. KubeletConfigKey: "kubelet",
  351. }}
  352. lkgStatus := lkgSource.DeepCopy()
  353. lkgStatus.ConfigMap.UID = combinedConfigMap.UID
  354. lkgStatus.ConfigMap.ResourceVersion = combinedConfigMap.ResourceVersion
  355. badSource := lkgSource.DeepCopy()
  356. badSource.ConfigMap.KubeletConfigKey = badConfigKey
  357. cases := []nodeConfigTestCase{
  358. {
  359. desc: "intended last-known-good",
  360. configSource: lkgSource,
  361. configMap: combinedConfigMap,
  362. expectConfigStatus: expectNodeConfigStatus{
  363. lastKnownGood: lkgStatus,
  364. },
  365. expectConfig: lkgKC,
  366. event: true,
  367. },
  368. {
  369. desc: "bad config",
  370. configSource: badSource,
  371. configMap: combinedConfigMap,
  372. expectConfigStatus: expectNodeConfigStatus{
  373. lastKnownGood: lkgStatus,
  374. err: status.LoadError,
  375. lkgActive: true,
  376. },
  377. expectConfig: lkgKC,
  378. event: true,
  379. },
  380. }
  381. // wait 12 minutes after setting the first config to ensure it has time to pass the trial duration
  382. testBothDirections(f, setConfigSourceFunc, &cases[0], cases[1:], 12*time.Minute)
  383. })
  384. })
  385. // previously, we missed a panic because we were not exercising this path
  386. Context("update Node.Spec.ConfigSource: non-nil last-known-good to a new non-nil last-known-good", func() {
  387. It(itDescription, func() {
  388. var err error
  389. // we base the "lkg" configmap off of the configuration from before the test
  390. lkgKC := beforeKC.DeepCopy()
  391. lkgConfigMap1 := newKubeletConfigMap("dynamic-kubelet-config-test-lkg-1", lkgKC)
  392. lkgConfigMap1, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(lkgConfigMap1)
  393. framework.ExpectNoError(err)
  394. lkgSource1 := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  395. Namespace: lkgConfigMap1.Namespace,
  396. Name: lkgConfigMap1.Name,
  397. KubeletConfigKey: "kubelet",
  398. }}
  399. lkgStatus1 := lkgSource1.DeepCopy()
  400. lkgStatus1.ConfigMap.UID = lkgConfigMap1.UID
  401. lkgStatus1.ConfigMap.ResourceVersion = lkgConfigMap1.ResourceVersion
  402. lkgConfigMap2 := newKubeletConfigMap("dynamic-kubelet-config-test-lkg-2", lkgKC)
  403. lkgConfigMap2, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(lkgConfigMap2)
  404. framework.ExpectNoError(err)
  405. lkgSource2 := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  406. Namespace: lkgConfigMap2.Namespace,
  407. Name: lkgConfigMap2.Name,
  408. KubeletConfigKey: "kubelet",
  409. }}
  410. lkgStatus2 := lkgSource2.DeepCopy()
  411. lkgStatus2.ConfigMap.UID = lkgConfigMap2.UID
  412. lkgStatus2.ConfigMap.ResourceVersion = lkgConfigMap2.ResourceVersion
  413. // cases
  414. first := nodeConfigTestCase{
  415. desc: "last-known-good-1",
  416. configSource: lkgSource1,
  417. configMap: lkgConfigMap1,
  418. expectConfigStatus: expectNodeConfigStatus{
  419. lastKnownGood: lkgStatus1,
  420. },
  421. expectConfig: lkgKC,
  422. event: true,
  423. }
  424. second := nodeConfigTestCase{
  425. desc: "last-known-good-2",
  426. configSource: lkgSource2,
  427. configMap: lkgConfigMap2,
  428. expectConfigStatus: expectNodeConfigStatus{
  429. lastKnownGood: lkgStatus2,
  430. },
  431. expectConfig: lkgKC,
  432. event: true,
  433. }
  434. // Manually actuate this to ensure we wait for each case to become the last-known-good
  435. const lkgDuration = 12 * time.Minute
  436. By(fmt.Sprintf("setting initial state %q", first.desc))
  437. first.run(f, setConfigSourceFunc, true, lkgDuration)
  438. By(fmt.Sprintf("from %q to %q", first.desc, second.desc))
  439. second.run(f, setConfigSourceFunc, true, lkgDuration)
  440. })
  441. })
  442. // exposes resource leaks across config changes
  443. Context("update Node.Spec.ConfigSource: 100 update stress test:", func() {
  444. It(itDescription, func() {
  445. var err error
  446. // we just create two configmaps with the same config but different names and toggle between them
  447. kc1 := beforeKC.DeepCopy()
  448. cm1 := newKubeletConfigMap("dynamic-kubelet-config-test-cm1", kc1)
  449. cm1, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(cm1)
  450. framework.ExpectNoError(err)
  451. // slightly change the config
  452. kc2 := kc1.DeepCopy()
  453. kc2.EventRecordQPS = kc1.EventRecordQPS + 1
  454. cm2 := newKubeletConfigMap("dynamic-kubelet-config-test-cm2", kc2)
  455. cm2, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(cm2)
  456. framework.ExpectNoError(err)
  457. cm1Source := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  458. Namespace: cm1.Namespace,
  459. Name: cm1.Name,
  460. KubeletConfigKey: "kubelet",
  461. }}
  462. cm2Source := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  463. Namespace: cm2.Namespace,
  464. Name: cm2.Name,
  465. KubeletConfigKey: "kubelet",
  466. }}
  467. cases := []nodeConfigTestCase{
  468. {
  469. desc: "cm1",
  470. configSource: cm1Source,
  471. configMap: cm1,
  472. expectConfig: kc1,
  473. event: true,
  474. },
  475. {
  476. desc: "cm2",
  477. configSource: cm2Source,
  478. configMap: cm2,
  479. expectConfig: kc2,
  480. event: true,
  481. },
  482. }
  483. for i := 0; i < 50; i++ { // change the config 101 times (changes 3 times in the first iteration, 2 times in each subsequent iteration)
  484. testBothDirections(f, setConfigSourceFunc, &cases[0], cases[1:], 0)
  485. }
  486. })
  487. })
  488. // Please note: This behavior is tested to ensure implementation correctness. We do not, however, recommend ConfigMap mutations
  489. // as a usage pattern for dynamic Kubelet config in large clusters. It is much safer to create a new ConfigMap, and incrementally
  490. // roll out a new Node.Spec.ConfigSource that references the new ConfigMap. In-place ConfigMap updates, including deletion
  491. // followed by re-creation, will cause all observing Kubelets to immediately restart for new config, because these operations
  492. // change the ResourceVersion of the ConfigMap.
  493. Context("update ConfigMap in-place: state transitions:", func() {
  494. It(itDescription, func() {
  495. var err error
  496. // we base the "correct" configmap off of the configuration from before the test
  497. correctKC := beforeKC.DeepCopy()
  498. correctConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-in-place", correctKC)
  499. correctConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(correctConfigMap)
  500. framework.ExpectNoError(err)
  501. // we reuse the same name, namespace
  502. failParseConfigMap := correctConfigMap.DeepCopy()
  503. failParseConfigMap.Data = map[string]string{
  504. "kubelet": "{0xdeadbeef}",
  505. }
  506. // fail to validate, we make a copy and set an invalid KubeAPIQPS on kc before serializing
  507. invalidKC := correctKC.DeepCopy()
  508. invalidKC.KubeAPIQPS = -1
  509. failValidateConfigMap := correctConfigMap.DeepCopy()
  510. failValidateConfigMap.Data = newKubeletConfigMap("", invalidKC).Data
  511. // ensure node config source is set to the config map we will mutate in-place,
  512. // since updateConfigMapFunc doesn't mutate Node.Spec.ConfigSource
  513. source := &apiv1.NodeConfigSource{
  514. ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  515. Namespace: correctConfigMap.Namespace,
  516. Name: correctConfigMap.Name,
  517. KubeletConfigKey: "kubelet",
  518. },
  519. }
  520. (&nodeConfigTestCase{
  521. desc: "initial state (correct)",
  522. configSource: source,
  523. configMap: correctConfigMap,
  524. expectConfig: correctKC,
  525. }).run(f, setConfigSourceFunc, false, 0)
  526. cases := []nodeConfigTestCase{
  527. {
  528. desc: "correct",
  529. configSource: source,
  530. configMap: correctConfigMap,
  531. expectConfig: correctKC,
  532. event: true,
  533. },
  534. {
  535. desc: "fail-parse",
  536. configSource: source,
  537. configMap: failParseConfigMap,
  538. expectConfigStatus: expectNodeConfigStatus{
  539. err: status.LoadError,
  540. lkgActive: true,
  541. },
  542. expectConfig: localKC,
  543. event: true,
  544. },
  545. {
  546. desc: "fail-validate",
  547. configSource: source,
  548. configMap: failValidateConfigMap,
  549. expectConfigStatus: expectNodeConfigStatus{
  550. err: status.ValidateError,
  551. lkgActive: true,
  552. },
  553. expectConfig: localKC,
  554. event: true,
  555. },
  556. }
  557. L := len(cases)
  558. for i := 1; i <= L; i++ { // need one less iteration than the number of cases
  559. testBothDirections(f, updateConfigMapFunc, &cases[i-1 : i][0], cases[i:L], 0)
  560. }
  561. })
  562. })
  563. // Please note: This behavior is tested to ensure implementation correctness. We do not, however, recommend ConfigMap mutations
  564. // as a usage pattern for dynamic Kubelet config in large clusters. It is much safer to create a new ConfigMap, and incrementally
  565. // roll out a new Node.Spec.ConfigSource that references the new ConfigMap. In-place ConfigMap updates, including deletion
  566. // followed by re-creation, will cause all observing Kubelets to immediately restart for new config, because these operations
  567. // change the ResourceVersion of the ConfigMap.
  568. Context("update ConfigMap in-place: recover to last-known-good version:", func() {
  569. It(itDescription, func() {
  570. var err error
  571. // we base the "lkg" configmap off of the configuration from before the test
  572. lkgKC := beforeKC.DeepCopy()
  573. lkgConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-in-place-lkg", lkgKC)
  574. lkgConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(lkgConfigMap)
  575. framework.ExpectNoError(err)
  576. // bad config map, we insert some bogus stuff into the configMap
  577. badConfigMap := lkgConfigMap.DeepCopy()
  578. badConfigMap.Data = map[string]string{
  579. "kubelet": "{0xdeadbeef}",
  580. }
  581. // ensure node config source is set to the config map we will mutate in-place
  582. source := &apiv1.NodeConfigSource{
  583. ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  584. Namespace: lkgConfigMap.Namespace,
  585. Name: lkgConfigMap.Name,
  586. KubeletConfigKey: "kubelet",
  587. },
  588. }
  589. // Even though the first test case will PUT the lkgConfigMap again, no-op writes don't increment
  590. // ResourceVersion, so the expected status we record here will still be correct.
  591. lkgStatus := source.DeepCopy()
  592. lkgStatus.ConfigMap.UID = lkgConfigMap.UID
  593. lkgStatus.ConfigMap.ResourceVersion = lkgConfigMap.ResourceVersion
  594. (&nodeConfigTestCase{
  595. desc: "initial state (correct)",
  596. configSource: source,
  597. configMap: lkgConfigMap,
  598. expectConfig: lkgKC,
  599. }).run(f, setConfigSourceFunc, false, 0) // wait 0 here, and we should not expect LastKnownGood to have changed yet (hence nil)
  600. cases := []nodeConfigTestCase{
  601. {
  602. desc: "intended last-known-good",
  603. configSource: source,
  604. configMap: lkgConfigMap,
  605. expectConfigStatus: expectNodeConfigStatus{
  606. lastKnownGood: lkgStatus,
  607. },
  608. expectConfig: lkgKC,
  609. event: true,
  610. },
  611. {
  612. // NOTE(mtaufen): If you see a strange "expected assigned x but got assigned y" error on this case,
  613. // it is possible that the Kubelet didn't start the informer that watches the currently assigned
  614. // ConfigMap, or didn't get updates from that informer. Other tests don't always catch this because
  615. // they quickly change config. The sync loop will always happen once, a bit after the Kubelet starts
  616. // up, because other informers' initial "add" events can queue a sync. If you wait long enough before
  617. // changing config (waiting for the config to become last-known-good, for example), the syncs queued by
  618. // add events will have already been processed, and the lack of a running ConfigMap informer will result
  619. // in a missed update, no config change, and the above error when we check the status.
  620. desc: "bad config",
  621. configSource: source,
  622. configMap: badConfigMap,
  623. expectConfigStatus: expectNodeConfigStatus{
  624. lastKnownGood: lkgStatus,
  625. err: status.LoadError,
  626. lkgActive: true,
  627. },
  628. expectConfig: lkgKC,
  629. event: true,
  630. },
  631. }
  632. // wait 12 minutes after setting the first config to ensure it has time to pass the trial duration
  633. testBothDirections(f, updateConfigMapFunc, &cases[0], cases[1:], 12*time.Minute)
  634. })
  635. })
  636. // Please note: This behavior is tested to ensure implementation correctness. We do not, however, recommend ConfigMap mutations
  637. // as a usage pattern for dynamic Kubelet config in large clusters. It is much safer to create a new ConfigMap, and incrementally
  638. // roll out a new Node.Spec.ConfigSource that references the new ConfigMap. In-place ConfigMap updates, including deletion
  639. // followed by re-creation, will cause all observing Kubelets to immediately restart for new config, because these operations
  640. // change the ResourceVersion of the ConfigMap.
  641. Context("delete and recreate ConfigMap: state transitions:", func() {
  642. It(itDescription, func() {
  643. var err error
  644. // we base the "correct" configmap off of the configuration from before the test
  645. correctKC := beforeKC.DeepCopy()
  646. correctConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-delete-createe", correctKC)
  647. correctConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(correctConfigMap)
  648. framework.ExpectNoError(err)
  649. // we reuse the same name, namespace
  650. failParseConfigMap := correctConfigMap.DeepCopy()
  651. failParseConfigMap.Data = map[string]string{
  652. "kubelet": "{0xdeadbeef}",
  653. }
  654. // fail to validate, we make a copy and set an invalid KubeAPIQPS on kc before serializing
  655. invalidKC := correctKC.DeepCopy()
  656. invalidKC.KubeAPIQPS = -1
  657. failValidateConfigMap := correctConfigMap.DeepCopy()
  658. failValidateConfigMap.Data = newKubeletConfigMap("", invalidKC).Data
  659. // ensure node config source is set to the config map we will mutate in-place,
  660. // since recreateConfigMapFunc doesn't mutate Node.Spec.ConfigSource
  661. source := &apiv1.NodeConfigSource{
  662. ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  663. Namespace: correctConfigMap.Namespace,
  664. Name: correctConfigMap.Name,
  665. KubeletConfigKey: "kubelet",
  666. },
  667. }
  668. (&nodeConfigTestCase{
  669. desc: "initial state (correct)",
  670. configSource: source,
  671. configMap: correctConfigMap,
  672. expectConfig: correctKC,
  673. }).run(f, setConfigSourceFunc, false, 0)
  674. cases := []nodeConfigTestCase{
  675. {
  676. desc: "correct",
  677. configSource: source,
  678. configMap: correctConfigMap,
  679. expectConfig: correctKC,
  680. event: true,
  681. },
  682. {
  683. desc: "fail-parse",
  684. configSource: source,
  685. configMap: failParseConfigMap,
  686. expectConfigStatus: expectNodeConfigStatus{
  687. err: status.LoadError,
  688. lkgActive: true,
  689. },
  690. expectConfig: localKC,
  691. event: true,
  692. },
  693. {
  694. desc: "fail-validate",
  695. configSource: source,
  696. configMap: failValidateConfigMap,
  697. expectConfigStatus: expectNodeConfigStatus{
  698. err: status.ValidateError,
  699. lkgActive: true,
  700. },
  701. expectConfig: localKC,
  702. event: true,
  703. },
  704. }
  705. L := len(cases)
  706. for i := 1; i <= L; i++ { // need one less iteration than the number of cases
  707. testBothDirections(f, recreateConfigMapFunc, &cases[i-1 : i][0], cases[i:L], 0)
  708. }
  709. })
  710. })
  711. // Please note: This behavior is tested to ensure implementation correctness. We do not, however, recommend ConfigMap mutations
  712. // as a usage pattern for dynamic Kubelet config in large clusters. It is much safer to create a new ConfigMap, and incrementally
  713. // roll out a new Node.Spec.ConfigSource that references the new ConfigMap. In-place ConfigMap updates, including deletion
  714. // followed by re-creation, will cause all observing Kubelets to immediately restart for new config, because these operations
  715. // change the ResourceVersion of the ConfigMap.
  716. Context("delete and recreate ConfigMap: error while ConfigMap is absent:", func() {
  717. It(itDescription, func() {
  718. var err error
  719. // we base the "correct" configmap off of the configuration from before the test
  720. correctKC := beforeKC.DeepCopy()
  721. correctConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-delete-createe", correctKC)
  722. correctConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(correctConfigMap)
  723. framework.ExpectNoError(err)
  724. // ensure node config source is set to the config map we will mutate in-place,
  725. // since our mutation functions don't mutate Node.Spec.ConfigSource
  726. source := &apiv1.NodeConfigSource{
  727. ConfigMap: &apiv1.ConfigMapNodeConfigSource{
  728. Namespace: correctConfigMap.Namespace,
  729. Name: correctConfigMap.Name,
  730. KubeletConfigKey: "kubelet",
  731. },
  732. }
  733. (&nodeConfigTestCase{
  734. desc: "correct",
  735. configSource: source,
  736. configMap: correctConfigMap,
  737. expectConfig: correctKC,
  738. }).run(f, setConfigSourceFunc, false, 0)
  739. // delete the ConfigMap, and ensure an error is reported by the Kubelet while the ConfigMap is absent
  740. (&nodeConfigTestCase{
  741. desc: "correct",
  742. configSource: source,
  743. configMap: correctConfigMap,
  744. expectConfigStatus: expectNodeConfigStatus{
  745. err: fmt.Sprintf(status.SyncErrorFmt, status.DownloadError),
  746. },
  747. expectConfig: correctKC,
  748. }).run(f, deleteConfigMapFunc, false, 0)
  749. // re-create the ConfigMap, and ensure the error disappears
  750. (&nodeConfigTestCase{
  751. desc: "correct",
  752. configSource: source,
  753. configMap: correctConfigMap,
  754. expectConfig: correctKC,
  755. }).run(f, createConfigMapFunc, false, 0)
  756. })
  757. })
  758. })
  759. })
  760. // testBothDirections tests the state change represented by each edge, where each case is a vertex,
  761. // and there are edges in each direction between first and each of the cases.
  762. func testBothDirections(f *framework.Framework, fn func(f *framework.Framework, tc *nodeConfigTestCase) error,
  763. first *nodeConfigTestCase, cases []nodeConfigTestCase, waitAfterFirst time.Duration) {
  764. // set to first and check that everything got set up properly
  765. By(fmt.Sprintf("setting initial state %q", first.desc))
  766. // we don't always expect an event here, because setting "first" might not represent
  767. // a change from the current configuration
  768. first.run(f, fn, false, waitAfterFirst)
  769. // for each case, set up, check expectations, then reset to first and check again
  770. for i := range cases {
  771. tc := &cases[i]
  772. By(fmt.Sprintf("from %q to %q", first.desc, tc.desc))
  773. // from first -> tc, tc.event fully describes whether we should get a config change event
  774. tc.run(f, fn, tc.event, 0)
  775. By(fmt.Sprintf("back to %q from %q", first.desc, tc.desc))
  776. // whether first -> tc should have produced a config change event partially determines whether tc -> first should produce an event
  777. first.run(f, fn, first.event && tc.event, 0)
  778. }
  779. }
  780. // run tests that, after performing fn, the node spec, status, configz, and latest event match
  781. // the expectations described by state.
  782. func (tc *nodeConfigTestCase) run(f *framework.Framework, fn func(f *framework.Framework, tc *nodeConfigTestCase) error,
  783. expectEvent bool, wait time.Duration) {
  784. // set the desired state, retry a few times in case we are competing with other editors
  785. Eventually(func() error {
  786. if err := fn(f, tc); err != nil {
  787. if len(tc.apierr) == 0 {
  788. return fmt.Errorf("case %s: expect nil error but got %q", tc.desc, err.Error())
  789. } else if !strings.Contains(err.Error(), tc.apierr) {
  790. return fmt.Errorf("case %s: expect error to contain %q but got %q", tc.desc, tc.apierr, err.Error())
  791. }
  792. } else if len(tc.apierr) > 0 {
  793. return fmt.Errorf("case %s: expect error to contain %q but got nil error", tc.desc, tc.apierr)
  794. }
  795. return nil
  796. }, time.Minute, time.Second).Should(BeNil())
  797. // skip further checks if we expected an API error
  798. if len(tc.apierr) > 0 {
  799. return
  800. }
  801. // wait for the designated duration before checking the reconciliation
  802. time.Sleep(wait)
  803. // check config source
  804. tc.checkNodeConfigSource(f)
  805. // check status
  806. tc.checkConfigStatus(f)
  807. // check that the Kubelet's config-related metrics are correct
  808. tc.checkConfigMetrics(f)
  809. // check expectConfig
  810. if tc.expectConfig != nil {
  811. tc.checkConfig(f)
  812. }
  813. // check that an event was sent for the config change
  814. if expectEvent {
  815. tc.checkEvent(f)
  816. }
  817. }
  818. // setConfigSourceFunc sets Node.Spec.ConfigSource to tc.configSource
  819. func setConfigSourceFunc(f *framework.Framework, tc *nodeConfigTestCase) error {
  820. return setNodeConfigSource(f, tc.configSource)
  821. }
  822. // updateConfigMapFunc updates the ConfigMap described by tc.configMap to contain matching data.
  823. // It also updates the resourceVersion in any non-nil NodeConfigSource.ConfigMap in the expected
  824. // status to match the resourceVersion of the updated ConfigMap.
  825. func updateConfigMapFunc(f *framework.Framework, tc *nodeConfigTestCase) error {
  826. // Clear ResourceVersion from the ConfigMap objects we use to initiate mutations
  827. // so that we don't get 409 (conflict) responses. ConfigMaps always allow updates
  828. // (with respect to concurrency control) when you omit ResourceVersion.
  829. // We know that we won't perform concurrent updates during this test.
  830. tc.configMap.ResourceVersion = ""
  831. cm, err := f.ClientSet.CoreV1().ConfigMaps(tc.configMap.Namespace).Update(tc.configMap)
  832. if err != nil {
  833. return err
  834. }
  835. // update tc.configMap's ResourceVersion to match the updated ConfigMap, this makes
  836. // sure our derived status checks have up-to-date information
  837. tc.configMap.ResourceVersion = cm.ResourceVersion
  838. return nil
  839. }
  840. // recreateConfigMapFunc deletes and recreates the ConfigMap described by tc.configMap.
  841. // The new ConfigMap will match tc.configMap.
  842. func recreateConfigMapFunc(f *framework.Framework, tc *nodeConfigTestCase) error {
  843. // need to ignore NotFound error, since there could be cases where delete
  844. // fails during a retry because the delete in a previous attempt succeeded,
  845. // before some other error occurred.
  846. err := deleteConfigMapFunc(f, tc)
  847. if err != nil && !apierrors.IsNotFound(err) {
  848. return err
  849. }
  850. return createConfigMapFunc(f, tc)
  851. }
  852. // deleteConfigMapFunc simply deletes tc.configMap
  853. func deleteConfigMapFunc(f *framework.Framework, tc *nodeConfigTestCase) error {
  854. return f.ClientSet.CoreV1().ConfigMaps(tc.configMap.Namespace).Delete(tc.configMap.Name, &metav1.DeleteOptions{})
  855. }
  856. // createConfigMapFunc creates tc.configMap and updates the UID and ResourceVersion on tc.configMap
  857. // to match the created configMap
  858. func createConfigMapFunc(f *framework.Framework, tc *nodeConfigTestCase) error {
  859. tc.configMap.ResourceVersion = ""
  860. cm, err := f.ClientSet.CoreV1().ConfigMaps(tc.configMap.Namespace).Create(tc.configMap)
  861. if err != nil {
  862. return err
  863. }
  864. // update tc.configMap's UID and ResourceVersion to match the new ConfigMap, this makes
  865. // sure our derived status checks have up-to-date information
  866. tc.configMap.UID = cm.UID
  867. tc.configMap.ResourceVersion = cm.ResourceVersion
  868. return nil
  869. }
  870. // make sure the node's config source matches what we expect, after setting it
  871. func (tc *nodeConfigTestCase) checkNodeConfigSource(f *framework.Framework) {
  872. const (
  873. timeout = time.Minute
  874. interval = time.Second
  875. )
  876. Eventually(func() error {
  877. node, err := f.ClientSet.CoreV1().Nodes().Get(framework.TestContext.NodeName, metav1.GetOptions{})
  878. if err != nil {
  879. return fmt.Errorf("checkNodeConfigSource: case %s: %v", tc.desc, err)
  880. }
  881. actual := node.Spec.ConfigSource
  882. if !apiequality.Semantic.DeepEqual(tc.configSource, actual) {
  883. return fmt.Errorf(spew.Sprintf("checkNodeConfigSource: case %s: expected %#v but got %#v", tc.desc, tc.configSource, actual))
  884. }
  885. return nil
  886. }, timeout, interval).Should(BeNil())
  887. }
  888. // make sure the node status eventually matches what we expect
  889. func (tc *nodeConfigTestCase) checkConfigStatus(f *framework.Framework) {
  890. const (
  891. timeout = time.Minute
  892. interval = time.Second
  893. )
  894. errFmt := fmt.Sprintf("checkConfigStatus: case %s:", tc.desc) + " %v"
  895. Eventually(func() error {
  896. node, err := f.ClientSet.CoreV1().Nodes().Get(framework.TestContext.NodeName, metav1.GetOptions{})
  897. if err != nil {
  898. return fmt.Errorf(errFmt, err)
  899. }
  900. if err := expectConfigStatus(tc, node.Status.Config); err != nil {
  901. return fmt.Errorf(errFmt, err)
  902. }
  903. return nil
  904. }, timeout, interval).Should(BeNil())
  905. }
  906. func expectConfigStatus(tc *nodeConfigTestCase, actual *apiv1.NodeConfigStatus) error {
  907. var errs []string
  908. if actual == nil {
  909. return fmt.Errorf("expectConfigStatus requires actual to be non-nil (possible Kubelet failed to update status)")
  910. }
  911. // check Assigned matches tc.configSource, with UID and ResourceVersion from tc.configMap
  912. expectAssigned := tc.configSource.DeepCopy()
  913. if expectAssigned != nil && expectAssigned.ConfigMap != nil {
  914. expectAssigned.ConfigMap.UID = tc.configMap.UID
  915. expectAssigned.ConfigMap.ResourceVersion = tc.configMap.ResourceVersion
  916. }
  917. if !apiequality.Semantic.DeepEqual(expectAssigned, actual.Assigned) {
  918. errs = append(errs, spew.Sprintf("expected Assigned %#v but got %#v", expectAssigned, actual.Assigned))
  919. }
  920. // check LastKnownGood matches tc.expectConfigStatus.lastKnownGood
  921. if !apiequality.Semantic.DeepEqual(tc.expectConfigStatus.lastKnownGood, actual.LastKnownGood) {
  922. errs = append(errs, spew.Sprintf("expected LastKnownGood %#v but got %#v", tc.expectConfigStatus.lastKnownGood, actual.LastKnownGood))
  923. }
  924. // check Active matches Assigned or LastKnownGood, depending on tc.expectConfigStatus.lkgActive
  925. expectActive := expectAssigned
  926. if tc.expectConfigStatus.lkgActive {
  927. expectActive = tc.expectConfigStatus.lastKnownGood
  928. }
  929. if !apiequality.Semantic.DeepEqual(expectActive, actual.Active) {
  930. errs = append(errs, spew.Sprintf("expected Active %#v but got %#v", expectActive, actual.Active))
  931. }
  932. // check Error
  933. if tc.expectConfigStatus.err != actual.Error {
  934. errs = append(errs, fmt.Sprintf("expected Error %q but got %q", tc.expectConfigStatus.err, actual.Error))
  935. }
  936. // format error list
  937. if len(errs) > 0 {
  938. return fmt.Errorf("%s", strings.Join(errs, ", "))
  939. }
  940. return nil
  941. }
  942. // make sure config exposed on configz matches what we expect
  943. func (tc *nodeConfigTestCase) checkConfig(f *framework.Framework) {
  944. const (
  945. timeout = time.Minute
  946. interval = time.Second
  947. )
  948. Eventually(func() error {
  949. actual, err := getCurrentKubeletConfig()
  950. if err != nil {
  951. return fmt.Errorf("checkConfig: case %s: %v", tc.desc, err)
  952. }
  953. if !apiequality.Semantic.DeepEqual(tc.expectConfig, actual) {
  954. return fmt.Errorf(spew.Sprintf("checkConfig: case %s: expected %#v but got %#v", tc.desc, tc.expectConfig, actual))
  955. }
  956. return nil
  957. }, timeout, interval).Should(BeNil())
  958. }
  959. // checkEvent makes sure an event was sent marking the Kubelet's restart to use new config,
  960. // and that it mentions the config we expect.
  961. func (tc *nodeConfigTestCase) checkEvent(f *framework.Framework) {
  962. const (
  963. timeout = time.Minute
  964. interval = time.Second
  965. )
  966. Eventually(func() error {
  967. events, err := f.ClientSet.CoreV1().Events("").List(metav1.ListOptions{})
  968. if err != nil {
  969. return fmt.Errorf("checkEvent: case %s: %v", tc.desc, err)
  970. }
  971. // find config changed event with most recent timestamp
  972. var recent *apiv1.Event
  973. for i := range events.Items {
  974. if events.Items[i].Reason == controller.KubeletConfigChangedEventReason {
  975. if recent == nil {
  976. recent = &events.Items[i]
  977. continue
  978. }
  979. // for these events, first and last timestamp are always the same
  980. if events.Items[i].FirstTimestamp.Time.After(recent.FirstTimestamp.Time) {
  981. recent = &events.Items[i]
  982. }
  983. }
  984. }
  985. // we expect at least one config change event
  986. if recent == nil {
  987. return fmt.Errorf("checkEvent: case %s: no events found with reason %s", tc.desc, controller.KubeletConfigChangedEventReason)
  988. }
  989. // construct expected message, based on the test case
  990. expectMessage := controller.LocalEventMessage
  991. if tc.configSource != nil {
  992. if tc.configSource.ConfigMap != nil {
  993. expectMessage = fmt.Sprintf(controller.RemoteEventMessageFmt,
  994. fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", tc.configSource.ConfigMap.Namespace, tc.configSource.ConfigMap.Name),
  995. tc.configMap.UID, tc.configMap.ResourceVersion, tc.configSource.ConfigMap.KubeletConfigKey)
  996. }
  997. }
  998. // compare messages
  999. if expectMessage != recent.Message {
  1000. return fmt.Errorf("checkEvent: case %s: expected event message %q but got %q", tc.desc, expectMessage, recent.Message)
  1001. }
  1002. return nil
  1003. }, timeout, interval).Should(BeNil())
  1004. }
  1005. // checkConfigMetrics makes sure the Kubelet's config related metrics are as we expect, given the test case
  1006. func (tc *nodeConfigTestCase) checkConfigMetrics(f *framework.Framework) {
  1007. const (
  1008. timeout = time.Minute
  1009. interval = time.Second
  1010. assignedConfigKey = metrics.KubeletSubsystem + "_" + metrics.AssignedConfigKey
  1011. activeConfigKey = metrics.KubeletSubsystem + "_" + metrics.ActiveConfigKey
  1012. lastKnownGoodConfigKey = metrics.KubeletSubsystem + "_" + metrics.LastKnownGoodConfigKey
  1013. configErrorKey = metrics.KubeletSubsystem + "_" + metrics.ConfigErrorKey
  1014. )
  1015. // local config helper
  1016. mkLocalSample := func(name model.LabelValue) *model.Sample {
  1017. return &model.Sample{
  1018. Metric: model.Metric(map[model.LabelName]model.LabelValue{
  1019. model.MetricNameLabel: name,
  1020. metrics.ConfigSourceLabelKey: metrics.ConfigSourceLabelValueLocal,
  1021. metrics.ConfigUIDLabelKey: "",
  1022. metrics.ConfigResourceVersionLabelKey: "",
  1023. metrics.KubeletConfigKeyLabelKey: "",
  1024. }),
  1025. Value: 1,
  1026. }
  1027. }
  1028. // remote config helper
  1029. mkRemoteSample := func(name model.LabelValue, source *apiv1.NodeConfigSource) *model.Sample {
  1030. return &model.Sample{
  1031. Metric: model.Metric(map[model.LabelName]model.LabelValue{
  1032. model.MetricNameLabel: name,
  1033. metrics.ConfigSourceLabelKey: model.LabelValue(fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", source.ConfigMap.Namespace, source.ConfigMap.Name)),
  1034. metrics.ConfigUIDLabelKey: model.LabelValue(source.ConfigMap.UID),
  1035. metrics.ConfigResourceVersionLabelKey: model.LabelValue(source.ConfigMap.ResourceVersion),
  1036. metrics.KubeletConfigKeyLabelKey: model.LabelValue(source.ConfigMap.KubeletConfigKey),
  1037. }),
  1038. Value: 1,
  1039. }
  1040. }
  1041. // error helper
  1042. mkErrorSample := func(expectError bool) *model.Sample {
  1043. v := model.SampleValue(0)
  1044. if expectError {
  1045. v = model.SampleValue(1)
  1046. }
  1047. return &model.Sample{
  1048. Metric: model.Metric(map[model.LabelName]model.LabelValue{model.MetricNameLabel: configErrorKey}),
  1049. Value: v,
  1050. }
  1051. }
  1052. // construct expected metrics
  1053. // assigned
  1054. assignedSamples := model.Samples{mkLocalSample(assignedConfigKey)}
  1055. assignedSource := tc.configSource.DeepCopy()
  1056. if assignedSource != nil && assignedSource.ConfigMap != nil {
  1057. assignedSource.ConfigMap.UID = tc.configMap.UID
  1058. assignedSource.ConfigMap.ResourceVersion = tc.configMap.ResourceVersion
  1059. assignedSamples = model.Samples{mkRemoteSample(assignedConfigKey, assignedSource)}
  1060. }
  1061. // last-known-good
  1062. lastKnownGoodSamples := model.Samples{mkLocalSample(lastKnownGoodConfigKey)}
  1063. lastKnownGoodSource := tc.expectConfigStatus.lastKnownGood
  1064. if lastKnownGoodSource != nil && lastKnownGoodSource.ConfigMap != nil {
  1065. lastKnownGoodSamples = model.Samples{mkRemoteSample(lastKnownGoodConfigKey, lastKnownGoodSource)}
  1066. }
  1067. // active
  1068. activeSamples := model.Samples{mkLocalSample(activeConfigKey)}
  1069. activeSource := assignedSource
  1070. if tc.expectConfigStatus.lkgActive {
  1071. activeSource = lastKnownGoodSource
  1072. }
  1073. if activeSource != nil && activeSource.ConfigMap != nil {
  1074. activeSamples = model.Samples{mkRemoteSample(activeConfigKey, activeSource)}
  1075. }
  1076. // error
  1077. errorSamples := model.Samples{mkErrorSample(len(tc.expectConfigStatus.err) > 0)}
  1078. // expected metrics
  1079. expect := frameworkmetrics.KubeletMetrics(map[string]model.Samples{
  1080. assignedConfigKey: assignedSamples,
  1081. activeConfigKey: activeSamples,
  1082. lastKnownGoodConfigKey: lastKnownGoodSamples,
  1083. configErrorKey: errorSamples,
  1084. })
  1085. // wait for expected metrics to appear
  1086. Eventually(func() error {
  1087. actual, err := getKubeletMetrics(sets.NewString(
  1088. assignedConfigKey,
  1089. activeConfigKey,
  1090. lastKnownGoodConfigKey,
  1091. configErrorKey,
  1092. ))
  1093. if err != nil {
  1094. return err
  1095. }
  1096. // clear timestamps from actual, so DeepEqual is time-invariant
  1097. for _, samples := range actual {
  1098. for _, sample := range samples {
  1099. sample.Timestamp = 0
  1100. }
  1101. }
  1102. // compare to expected
  1103. if !reflect.DeepEqual(expect, actual) {
  1104. return fmt.Errorf("checkConfigMetrics: case: %s: expect metrics %s but got %s", tc.desc, spew.Sprintf("%#v", expect), spew.Sprintf("%#v", actual))
  1105. }
  1106. return nil
  1107. }, timeout, interval).Should(BeNil())
  1108. }
  1109. // constructs the expected SelfLink for a config map
  1110. func configMapAPIPath(cm *apiv1.ConfigMap) string {
  1111. return fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", cm.Namespace, cm.Name)
  1112. }