dynamic_kubelet_config_test.go 47 KB

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