kubelet_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /*
  2. Copyright 2019 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 componentconfigs
  14. import (
  15. "path/filepath"
  16. "reflect"
  17. "strings"
  18. "testing"
  19. "github.com/lithammer/dedent"
  20. v1 "k8s.io/api/core/v1"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/util/version"
  23. clientsetfake "k8s.io/client-go/kubernetes/fake"
  24. kubeletconfig "k8s.io/kubelet/config/v1beta1"
  25. utilpointer "k8s.io/utils/pointer"
  26. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  27. kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
  28. "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  29. "k8s.io/kubernetes/cmd/kubeadm/app/features"
  30. kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
  31. )
  32. // kubeletMarshalCases holds common marshal test cases for both the marshal and unmarshal tests
  33. var kubeletMarshalCases = []struct {
  34. name string
  35. obj *kubeletConfig
  36. yaml string
  37. }{
  38. {
  39. name: "Empty config",
  40. obj: &kubeletConfig{
  41. config: kubeletconfig.KubeletConfiguration{},
  42. },
  43. yaml: dedent.Dedent(`
  44. apiVersion: kubelet.config.k8s.io/v1beta1
  45. authentication:
  46. anonymous: {}
  47. webhook:
  48. cacheTTL: 0s
  49. x509: {}
  50. authorization:
  51. webhook:
  52. cacheAuthorizedTTL: 0s
  53. cacheUnauthorizedTTL: 0s
  54. cpuManagerReconcilePeriod: 0s
  55. evictionPressureTransitionPeriod: 0s
  56. fileCheckFrequency: 0s
  57. httpCheckFrequency: 0s
  58. imageMinimumGCAge: 0s
  59. kind: KubeletConfiguration
  60. nodeStatusReportFrequency: 0s
  61. nodeStatusUpdateFrequency: 0s
  62. runtimeRequestTimeout: 0s
  63. streamingConnectionIdleTimeout: 0s
  64. syncFrequency: 0s
  65. volumeStatsAggPeriod: 0s
  66. `),
  67. },
  68. {
  69. name: "Non empty config",
  70. obj: &kubeletConfig{
  71. config: kubeletconfig.KubeletConfiguration{
  72. Address: "1.2.3.4",
  73. Port: 12345,
  74. RotateCertificates: true,
  75. },
  76. },
  77. yaml: dedent.Dedent(`
  78. address: 1.2.3.4
  79. apiVersion: kubelet.config.k8s.io/v1beta1
  80. authentication:
  81. anonymous: {}
  82. webhook:
  83. cacheTTL: 0s
  84. x509: {}
  85. authorization:
  86. webhook:
  87. cacheAuthorizedTTL: 0s
  88. cacheUnauthorizedTTL: 0s
  89. cpuManagerReconcilePeriod: 0s
  90. evictionPressureTransitionPeriod: 0s
  91. fileCheckFrequency: 0s
  92. httpCheckFrequency: 0s
  93. imageMinimumGCAge: 0s
  94. kind: KubeletConfiguration
  95. nodeStatusReportFrequency: 0s
  96. nodeStatusUpdateFrequency: 0s
  97. port: 12345
  98. rotateCertificates: true
  99. runtimeRequestTimeout: 0s
  100. streamingConnectionIdleTimeout: 0s
  101. syncFrequency: 0s
  102. volumeStatsAggPeriod: 0s
  103. `),
  104. },
  105. }
  106. func TestKubeletMarshal(t *testing.T) {
  107. for _, test := range kubeletMarshalCases {
  108. t.Run(test.name, func(t *testing.T) {
  109. b, err := test.obj.Marshal()
  110. if err != nil {
  111. t.Fatalf("Marshal failed: %v", err)
  112. }
  113. got := strings.TrimSpace(string(b))
  114. expected := strings.TrimSpace(test.yaml)
  115. if expected != string(got) {
  116. t.Fatalf("Missmatch between expected and got:\nExpected:\n%s\n---\nGot:\n%s", expected, string(got))
  117. }
  118. })
  119. }
  120. }
  121. func TestKubeletUnmarshal(t *testing.T) {
  122. for _, test := range kubeletMarshalCases {
  123. t.Run(test.name, func(t *testing.T) {
  124. gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(test.yaml))
  125. if err != nil {
  126. t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
  127. }
  128. got := &kubeletConfig{}
  129. if err = got.Unmarshal(gvkmap); err != nil {
  130. t.Fatalf("unexpected failure of Unmarshal: %v", err)
  131. }
  132. expected := test.obj.DeepCopy().(*kubeletConfig)
  133. expected.config.APIVersion = kubeletHandler.GroupVersion.String()
  134. expected.config.Kind = "KubeletConfiguration"
  135. if !reflect.DeepEqual(got, expected) {
  136. t.Fatalf("Missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", expected, got)
  137. }
  138. })
  139. }
  140. }
  141. func TestKubeletDefault(t *testing.T) {
  142. tests := []struct {
  143. name string
  144. clusterCfg kubeadmapi.ClusterConfiguration
  145. expected kubeletConfig
  146. }{
  147. {
  148. name: "No specific defaulting works",
  149. clusterCfg: kubeadmapi.ClusterConfiguration{},
  150. expected: kubeletConfig{
  151. config: kubeletconfig.KubeletConfiguration{
  152. FeatureGates: map[string]bool{},
  153. StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
  154. ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP},
  155. Authentication: kubeletconfig.KubeletAuthentication{
  156. X509: kubeletconfig.KubeletX509Authentication{
  157. ClientCAFile: constants.CACertName,
  158. },
  159. Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
  160. Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
  161. },
  162. Webhook: kubeletconfig.KubeletWebhookAuthentication{
  163. Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
  164. },
  165. },
  166. Authorization: kubeletconfig.KubeletAuthorization{
  167. Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
  168. },
  169. HealthzBindAddress: kubeletHealthzBindAddress,
  170. HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
  171. RotateCertificates: kubeletRotateCertificates,
  172. },
  173. },
  174. },
  175. {
  176. name: "Service subnet, no dual stack defaulting works",
  177. clusterCfg: kubeadmapi.ClusterConfiguration{
  178. Networking: kubeadmapi.Networking{
  179. ServiceSubnet: "192.168.0.0/16",
  180. },
  181. },
  182. expected: kubeletConfig{
  183. config: kubeletconfig.KubeletConfiguration{
  184. FeatureGates: map[string]bool{},
  185. StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
  186. ClusterDNS: []string{"192.168.0.10"},
  187. Authentication: kubeletconfig.KubeletAuthentication{
  188. X509: kubeletconfig.KubeletX509Authentication{
  189. ClientCAFile: constants.CACertName,
  190. },
  191. Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
  192. Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
  193. },
  194. Webhook: kubeletconfig.KubeletWebhookAuthentication{
  195. Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
  196. },
  197. },
  198. Authorization: kubeletconfig.KubeletAuthorization{
  199. Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
  200. },
  201. HealthzBindAddress: kubeletHealthzBindAddress,
  202. HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
  203. RotateCertificates: kubeletRotateCertificates,
  204. },
  205. },
  206. },
  207. {
  208. name: "Service subnet, dual stack defaulting works",
  209. clusterCfg: kubeadmapi.ClusterConfiguration{
  210. FeatureGates: map[string]bool{
  211. features.IPv6DualStack: true,
  212. },
  213. Networking: kubeadmapi.Networking{
  214. ServiceSubnet: "192.168.0.0/16",
  215. },
  216. },
  217. expected: kubeletConfig{
  218. config: kubeletconfig.KubeletConfiguration{
  219. FeatureGates: map[string]bool{},
  220. StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
  221. ClusterDNS: []string{"192.168.0.10"},
  222. Authentication: kubeletconfig.KubeletAuthentication{
  223. X509: kubeletconfig.KubeletX509Authentication{
  224. ClientCAFile: constants.CACertName,
  225. },
  226. Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
  227. Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
  228. },
  229. Webhook: kubeletconfig.KubeletWebhookAuthentication{
  230. Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
  231. },
  232. },
  233. Authorization: kubeletconfig.KubeletAuthorization{
  234. Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
  235. },
  236. HealthzBindAddress: kubeletHealthzBindAddress,
  237. HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
  238. RotateCertificates: kubeletRotateCertificates,
  239. },
  240. },
  241. },
  242. {
  243. name: "DNS domain defaulting works",
  244. clusterCfg: kubeadmapi.ClusterConfiguration{
  245. Networking: kubeadmapi.Networking{
  246. DNSDomain: "example.com",
  247. },
  248. },
  249. expected: kubeletConfig{
  250. config: kubeletconfig.KubeletConfiguration{
  251. FeatureGates: map[string]bool{},
  252. StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
  253. ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP},
  254. ClusterDomain: "example.com",
  255. Authentication: kubeletconfig.KubeletAuthentication{
  256. X509: kubeletconfig.KubeletX509Authentication{
  257. ClientCAFile: constants.CACertName,
  258. },
  259. Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
  260. Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
  261. },
  262. Webhook: kubeletconfig.KubeletWebhookAuthentication{
  263. Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
  264. },
  265. },
  266. Authorization: kubeletconfig.KubeletAuthorization{
  267. Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
  268. },
  269. HealthzBindAddress: kubeletHealthzBindAddress,
  270. HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
  271. RotateCertificates: kubeletRotateCertificates,
  272. },
  273. },
  274. },
  275. {
  276. name: "CertificatesDir defaulting works",
  277. clusterCfg: kubeadmapi.ClusterConfiguration{
  278. CertificatesDir: "/path/to/certs",
  279. },
  280. expected: kubeletConfig{
  281. config: kubeletconfig.KubeletConfiguration{
  282. FeatureGates: map[string]bool{},
  283. StaticPodPath: kubeadmapiv1beta2.DefaultManifestsDir,
  284. ClusterDNS: []string{kubeadmapiv1beta2.DefaultClusterDNSIP},
  285. Authentication: kubeletconfig.KubeletAuthentication{
  286. X509: kubeletconfig.KubeletX509Authentication{
  287. ClientCAFile: filepath.Join("/path/to/certs", constants.CACertName),
  288. },
  289. Anonymous: kubeletconfig.KubeletAnonymousAuthentication{
  290. Enabled: utilpointer.BoolPtr(kubeletAuthenticationAnonymousEnabled),
  291. },
  292. Webhook: kubeletconfig.KubeletWebhookAuthentication{
  293. Enabled: utilpointer.BoolPtr(kubeletAuthenticationWebhookEnabled),
  294. },
  295. },
  296. Authorization: kubeletconfig.KubeletAuthorization{
  297. Mode: kubeletconfig.KubeletAuthorizationModeWebhook,
  298. },
  299. HealthzBindAddress: kubeletHealthzBindAddress,
  300. HealthzPort: utilpointer.Int32Ptr(constants.KubeletHealthzPort),
  301. RotateCertificates: kubeletRotateCertificates,
  302. },
  303. },
  304. },
  305. }
  306. for _, test := range tests {
  307. t.Run(test.name, func(t *testing.T) {
  308. got := &kubeletConfig{}
  309. got.Default(&test.clusterCfg, &kubeadmapi.APIEndpoint{})
  310. if !reflect.DeepEqual(got, &test.expected) {
  311. t.Fatalf("Missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", test.expected, got)
  312. }
  313. })
  314. }
  315. }
  316. // runKubeletFromTest holds common test case data and evaluation code for kubeletHandler.From* functions
  317. func runKubeletFromTest(t *testing.T, perform func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error)) {
  318. tests := []struct {
  319. name string
  320. in string
  321. out *kubeletConfig
  322. expectErr bool
  323. }{
  324. {
  325. name: "Empty document map should return nothing successfully",
  326. },
  327. {
  328. name: "Non-empty non-kubelet document map returns nothing successfully",
  329. in: dedent.Dedent(`
  330. apiVersion: api.example.com/v1
  331. kind: Configuration
  332. `),
  333. },
  334. {
  335. name: "Old kubelet version returns an error",
  336. in: dedent.Dedent(`
  337. apiVersion: kubelet.config.k8s.io/v1alpha1
  338. kind: KubeletConfiguration
  339. `),
  340. expectErr: true,
  341. },
  342. {
  343. name: "New kubelet version returns an error",
  344. in: dedent.Dedent(`
  345. apiVersion: kubelet.config.k8s.io/v1
  346. kind: KubeletConfiguration
  347. `),
  348. expectErr: true,
  349. },
  350. {
  351. name: "Wrong kubelet kind returns an error",
  352. in: dedent.Dedent(`
  353. apiVersion: kubelet.config.k8s.io/v1beta1
  354. kind: Configuration
  355. `),
  356. expectErr: true,
  357. },
  358. {
  359. name: "Valid kubelet only config gets loaded",
  360. in: dedent.Dedent(`
  361. apiVersion: kubelet.config.k8s.io/v1beta1
  362. kind: KubeletConfiguration
  363. address: 1.2.3.4
  364. port: 12345
  365. rotateCertificates: true
  366. `),
  367. out: &kubeletConfig{
  368. config: kubeletconfig.KubeletConfiguration{
  369. TypeMeta: metav1.TypeMeta{
  370. APIVersion: kubeletHandler.GroupVersion.String(),
  371. Kind: "KubeletConfiguration",
  372. },
  373. Address: "1.2.3.4",
  374. Port: 12345,
  375. RotateCertificates: true,
  376. },
  377. },
  378. },
  379. {
  380. name: "Valid kubelet config gets loaded when coupled with an extra document",
  381. in: dedent.Dedent(`
  382. apiVersion: api.example.com/v1
  383. kind: Configuration
  384. ---
  385. apiVersion: kubelet.config.k8s.io/v1beta1
  386. kind: KubeletConfiguration
  387. address: 1.2.3.4
  388. port: 12345
  389. rotateCertificates: true
  390. `),
  391. out: &kubeletConfig{
  392. config: kubeletconfig.KubeletConfiguration{
  393. TypeMeta: metav1.TypeMeta{
  394. APIVersion: kubeletHandler.GroupVersion.String(),
  395. Kind: "KubeletConfiguration",
  396. },
  397. Address: "1.2.3.4",
  398. Port: 12345,
  399. RotateCertificates: true,
  400. },
  401. },
  402. },
  403. }
  404. for _, test := range tests {
  405. t.Run(test.name, func(t *testing.T) {
  406. componentCfg, err := perform(t, test.in)
  407. if err != nil {
  408. if !test.expectErr {
  409. t.Errorf("unexpected failure: %v", err)
  410. }
  411. } else {
  412. if test.expectErr {
  413. t.Error("unexpected success")
  414. } else {
  415. if componentCfg == nil {
  416. if test.out != nil {
  417. t.Error("unexpected nil result")
  418. }
  419. } else {
  420. if got, ok := componentCfg.(*kubeletConfig); !ok {
  421. t.Error("different result type")
  422. } else {
  423. if test.out == nil {
  424. t.Errorf("unexpected result: %v", got)
  425. } else if !reflect.DeepEqual(test.out, got) {
  426. t.Errorf("missmatch between expected and got:\nExpected:\n%v\n---\nGot:\n%v", test.out, got)
  427. }
  428. }
  429. }
  430. }
  431. }
  432. })
  433. }
  434. }
  435. func TestKubeletFromDocumentMap(t *testing.T) {
  436. runKubeletFromTest(t, func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error) {
  437. gvkmap, err := kubeadmutil.SplitYAMLDocuments([]byte(in))
  438. if err != nil {
  439. t.Fatalf("unexpected failure of SplitYAMLDocuments: %v", err)
  440. }
  441. return kubeletHandler.FromDocumentMap(gvkmap)
  442. })
  443. }
  444. func TestKubeletFromCluster(t *testing.T) {
  445. runKubeletFromTest(t, func(t *testing.T, in string) (kubeadmapi.ComponentConfig, error) {
  446. clusterCfg := &kubeadmapi.ClusterConfiguration{
  447. KubernetesVersion: constants.CurrentKubernetesVersion.String(),
  448. }
  449. k8sVersion := version.MustParseGeneric(clusterCfg.KubernetesVersion)
  450. client := clientsetfake.NewSimpleClientset(
  451. &v1.ConfigMap{
  452. ObjectMeta: metav1.ObjectMeta{
  453. Name: constants.GetKubeletConfigMapName(k8sVersion),
  454. Namespace: metav1.NamespaceSystem,
  455. },
  456. Data: map[string]string{
  457. constants.KubeletBaseConfigurationConfigMapKey: in,
  458. },
  459. },
  460. )
  461. return kubeletHandler.FromCluster(client, clusterCfg)
  462. })
  463. }