staticpods_test.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package upgrade
  14. import (
  15. "crypto/sha256"
  16. "crypto/x509"
  17. "fmt"
  18. "io/ioutil"
  19. "math/big"
  20. "os"
  21. "path/filepath"
  22. "strings"
  23. "testing"
  24. "time"
  25. "github.com/coreos/etcd/clientv3"
  26. "github.com/coreos/etcd/pkg/transport"
  27. "github.com/pkg/errors"
  28. "k8s.io/client-go/tools/clientcmd"
  29. certutil "k8s.io/client-go/util/cert"
  30. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  31. "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  32. kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  33. certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
  34. "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/renewal"
  35. controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
  36. etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
  37. kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
  38. "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
  39. certstestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs"
  40. configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
  41. etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
  42. "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
  43. testutil "k8s.io/kubernetes/cmd/kubeadm/test"
  44. )
  45. const (
  46. waitForHashes = "wait-for-hashes"
  47. waitForHashChange = "wait-for-hash-change"
  48. waitForPodsWithLabel = "wait-for-pods-with-label"
  49. testConfiguration = `
  50. apiVersion: kubeadm.k8s.io/v1beta2
  51. kind: InitConfiguration
  52. nodeRegistration:
  53. name: foo
  54. criSocket: ""
  55. localAPIEndpoint:
  56. advertiseAddress: 192.168.2.2
  57. bindPort: 6443
  58. bootstrapTokens:
  59. - token: ce3aa5.5ec8455bb76b379f
  60. ttl: 24h
  61. ---
  62. apiVersion: kubeadm.k8s.io/v1beta2
  63. kind: ClusterConfiguration
  64. apiServer:
  65. certSANs: null
  66. extraArgs: null
  67. certificatesDir: %s
  68. etcd:
  69. local:
  70. dataDir: %s
  71. image: ""
  72. imageRepository: k8s.gcr.io
  73. kubernetesVersion: %s
  74. networking:
  75. dnsDomain: cluster.local
  76. podSubnet: ""
  77. serviceSubnet: 10.96.0.0/12
  78. useHyperKubeImage: false
  79. `
  80. )
  81. // fakeWaiter is a fake apiclient.Waiter that returns errors it was initialized with
  82. type fakeWaiter struct {
  83. errsToReturn map[string]error
  84. }
  85. func NewFakeStaticPodWaiter(errsToReturn map[string]error) apiclient.Waiter {
  86. return &fakeWaiter{
  87. errsToReturn: errsToReturn,
  88. }
  89. }
  90. // WaitForAPI just returns a dummy nil, to indicate that the program should just proceed
  91. func (w *fakeWaiter) WaitForAPI() error {
  92. return nil
  93. }
  94. // WaitForPodsWithLabel just returns an error if set from errsToReturn
  95. func (w *fakeWaiter) WaitForPodsWithLabel(kvLabel string) error {
  96. return w.errsToReturn[waitForPodsWithLabel]
  97. }
  98. // WaitForPodToDisappear just returns a dummy nil, to indicate that the program should just proceed
  99. func (w *fakeWaiter) WaitForPodToDisappear(podName string) error {
  100. return nil
  101. }
  102. // SetTimeout is a no-op; we don't use it in this implementation
  103. func (w *fakeWaiter) SetTimeout(_ time.Duration) {}
  104. // WaitForStaticPodControlPlaneHashes returns an error if set from errsToReturn
  105. func (w *fakeWaiter) WaitForStaticPodControlPlaneHashes(_ string) (map[string]string, error) {
  106. return map[string]string{}, w.errsToReturn[waitForHashes]
  107. }
  108. // WaitForStaticPodSingleHash returns an error if set from errsToReturn
  109. func (w *fakeWaiter) WaitForStaticPodSingleHash(_ string, _ string) (string, error) {
  110. return "", w.errsToReturn[waitForHashes]
  111. }
  112. // WaitForStaticPodHashChange returns an error if set from errsToReturn
  113. func (w *fakeWaiter) WaitForStaticPodHashChange(_, _, _ string) error {
  114. return w.errsToReturn[waitForHashChange]
  115. }
  116. // WaitForHealthyKubelet returns a dummy nil just to implement the interface
  117. func (w *fakeWaiter) WaitForHealthyKubelet(_ time.Duration, _ string) error {
  118. return nil
  119. }
  120. // WaitForKubeletAndFunc is a wrapper for WaitForHealthyKubelet that also blocks for a function
  121. func (w *fakeWaiter) WaitForKubeletAndFunc(f func() error) error {
  122. return nil
  123. }
  124. type fakeStaticPodPathManager struct {
  125. kubernetesDir string
  126. realManifestDir string
  127. tempManifestDir string
  128. backupManifestDir string
  129. backupEtcdDir string
  130. MoveFileFunc func(string, string) error
  131. }
  132. func NewFakeStaticPodPathManager(moveFileFunc func(string, string) error) (StaticPodPathManager, error) {
  133. kubernetesDir, err := ioutil.TempDir("", "kubeadm-pathmanager-")
  134. if err != nil {
  135. return nil, errors.Wrapf(err, "couldn't create a temporary directory for the upgrade")
  136. }
  137. realManifestDir := filepath.Join(kubernetesDir, constants.ManifestsSubDirName)
  138. if err := os.Mkdir(realManifestDir, 0700); err != nil {
  139. return nil, errors.Wrapf(err, "couldn't create a realManifestDir for the upgrade")
  140. }
  141. upgradedManifestDir := filepath.Join(kubernetesDir, "upgraded-manifests")
  142. if err := os.Mkdir(upgradedManifestDir, 0700); err != nil {
  143. return nil, errors.Wrapf(err, "couldn't create a upgradedManifestDir for the upgrade")
  144. }
  145. backupManifestDir := filepath.Join(kubernetesDir, "backup-manifests")
  146. if err := os.Mkdir(backupManifestDir, 0700); err != nil {
  147. return nil, errors.Wrap(err, "couldn't create a backupManifestDir for the upgrade")
  148. }
  149. backupEtcdDir := filepath.Join(kubernetesDir, "kubeadm-backup-etcd")
  150. if err := os.Mkdir(backupEtcdDir, 0700); err != nil {
  151. return nil, err
  152. }
  153. return &fakeStaticPodPathManager{
  154. kubernetesDir: kubernetesDir,
  155. realManifestDir: realManifestDir,
  156. tempManifestDir: upgradedManifestDir,
  157. backupManifestDir: backupManifestDir,
  158. backupEtcdDir: backupEtcdDir,
  159. MoveFileFunc: moveFileFunc,
  160. }, nil
  161. }
  162. func (spm *fakeStaticPodPathManager) MoveFile(oldPath, newPath string) error {
  163. return spm.MoveFileFunc(oldPath, newPath)
  164. }
  165. func (spm *fakeStaticPodPathManager) KubernetesDir() string {
  166. return spm.kubernetesDir
  167. }
  168. func (spm *fakeStaticPodPathManager) RealManifestPath(component string) string {
  169. return constants.GetStaticPodFilepath(component, spm.realManifestDir)
  170. }
  171. func (spm *fakeStaticPodPathManager) RealManifestDir() string {
  172. return spm.realManifestDir
  173. }
  174. func (spm *fakeStaticPodPathManager) TempManifestPath(component string) string {
  175. return constants.GetStaticPodFilepath(component, spm.tempManifestDir)
  176. }
  177. func (spm *fakeStaticPodPathManager) TempManifestDir() string {
  178. return spm.tempManifestDir
  179. }
  180. func (spm *fakeStaticPodPathManager) BackupManifestPath(component string) string {
  181. return constants.GetStaticPodFilepath(component, spm.backupManifestDir)
  182. }
  183. func (spm *fakeStaticPodPathManager) BackupManifestDir() string {
  184. return spm.backupManifestDir
  185. }
  186. func (spm *fakeStaticPodPathManager) BackupEtcdDir() string {
  187. return spm.backupEtcdDir
  188. }
  189. func (spm *fakeStaticPodPathManager) CleanupDirs() error {
  190. if err := os.RemoveAll(spm.TempManifestDir()); err != nil {
  191. return err
  192. }
  193. if err := os.RemoveAll(spm.BackupManifestDir()); err != nil {
  194. return err
  195. }
  196. return os.RemoveAll(spm.BackupEtcdDir())
  197. }
  198. type fakeTLSEtcdClient struct{ TLS bool }
  199. func (c fakeTLSEtcdClient) ClusterAvailable() (bool, error) { return true, nil }
  200. func (c fakeTLSEtcdClient) WaitForClusterAvailable(retries int, retryInterval time.Duration) (bool, error) {
  201. return true, nil
  202. }
  203. func (c fakeTLSEtcdClient) GetClusterStatus() (map[string]*clientv3.StatusResponse, error) {
  204. return map[string]*clientv3.StatusResponse{
  205. "https://1.2.3.4:2379": {
  206. Version: "3.1.12",
  207. }}, nil
  208. }
  209. func (c fakeTLSEtcdClient) GetClusterVersions() (map[string]string, error) {
  210. return map[string]string{
  211. "https://1.2.3.4:2379": "3.1.12",
  212. }, nil
  213. }
  214. func (c fakeTLSEtcdClient) GetVersion() (string, error) {
  215. return "3.1.12", nil
  216. }
  217. func (c fakeTLSEtcdClient) Sync() error { return nil }
  218. func (c fakeTLSEtcdClient) AddMember(name string, peerAddrs string) ([]etcdutil.Member, error) {
  219. return []etcdutil.Member{}, nil
  220. }
  221. func (c fakeTLSEtcdClient) GetMemberID(peerURL string) (uint64, error) {
  222. return 0, nil
  223. }
  224. func (c fakeTLSEtcdClient) RemoveMember(id uint64) ([]etcdutil.Member, error) {
  225. return []etcdutil.Member{}, nil
  226. }
  227. type fakePodManifestEtcdClient struct{ ManifestDir, CertificatesDir string }
  228. func (c fakePodManifestEtcdClient) ClusterAvailable() (bool, error) { return true, nil }
  229. func (c fakePodManifestEtcdClient) WaitForClusterAvailable(retries int, retryInterval time.Duration) (bool, error) {
  230. return true, nil
  231. }
  232. func (c fakePodManifestEtcdClient) GetClusterStatus() (map[string]*clientv3.StatusResponse, error) {
  233. // Make sure the certificates generated from the upgrade are readable from disk
  234. tlsInfo := transport.TLSInfo{
  235. CertFile: filepath.Join(c.CertificatesDir, constants.EtcdCACertName),
  236. KeyFile: filepath.Join(c.CertificatesDir, constants.EtcdHealthcheckClientCertName),
  237. TrustedCAFile: filepath.Join(c.CertificatesDir, constants.EtcdHealthcheckClientKeyName),
  238. }
  239. _, err := tlsInfo.ClientConfig()
  240. if err != nil {
  241. return nil, err
  242. }
  243. return map[string]*clientv3.StatusResponse{
  244. "https://1.2.3.4:2379": {Version: "3.1.12"},
  245. }, nil
  246. }
  247. func (c fakePodManifestEtcdClient) GetClusterVersions() (map[string]string, error) {
  248. return map[string]string{
  249. "https://1.2.3.4:2379": "3.1.12",
  250. }, nil
  251. }
  252. func (c fakePodManifestEtcdClient) GetVersion() (string, error) {
  253. return "3.1.12", nil
  254. }
  255. func (c fakePodManifestEtcdClient) Sync() error { return nil }
  256. func (c fakePodManifestEtcdClient) AddMember(name string, peerAddrs string) ([]etcdutil.Member, error) {
  257. return []etcdutil.Member{}, nil
  258. }
  259. func (c fakePodManifestEtcdClient) GetMemberID(peerURL string) (uint64, error) {
  260. return 0, nil
  261. }
  262. func (c fakePodManifestEtcdClient) RemoveMember(id uint64) ([]etcdutil.Member, error) {
  263. return []etcdutil.Member{}, nil
  264. }
  265. func TestStaticPodControlPlane(t *testing.T) {
  266. tests := []struct {
  267. description string
  268. waitErrsToReturn map[string]error
  269. moveFileFunc func(string, string) error
  270. skipKubeConfig string
  271. expectedErr bool
  272. manifestShouldChange bool
  273. }{
  274. {
  275. description: "error-free case should succeed",
  276. waitErrsToReturn: map[string]error{
  277. waitForHashes: nil,
  278. waitForHashChange: nil,
  279. waitForPodsWithLabel: nil,
  280. },
  281. moveFileFunc: func(oldPath, newPath string) error {
  282. return os.Rename(oldPath, newPath)
  283. },
  284. expectedErr: false,
  285. manifestShouldChange: true,
  286. },
  287. {
  288. description: "any wait error should result in a rollback and an abort",
  289. waitErrsToReturn: map[string]error{
  290. waitForHashes: errors.New("boo! failed"),
  291. waitForHashChange: nil,
  292. waitForPodsWithLabel: nil,
  293. },
  294. moveFileFunc: func(oldPath, newPath string) error {
  295. return os.Rename(oldPath, newPath)
  296. },
  297. expectedErr: true,
  298. manifestShouldChange: false,
  299. },
  300. {
  301. description: "any wait error should result in a rollback and an abort",
  302. waitErrsToReturn: map[string]error{
  303. waitForHashes: nil,
  304. waitForHashChange: errors.New("boo! failed"),
  305. waitForPodsWithLabel: nil,
  306. },
  307. moveFileFunc: func(oldPath, newPath string) error {
  308. return os.Rename(oldPath, newPath)
  309. },
  310. expectedErr: true,
  311. manifestShouldChange: false,
  312. },
  313. {
  314. description: "any wait error should result in a rollback and an abort",
  315. waitErrsToReturn: map[string]error{
  316. waitForHashes: nil,
  317. waitForHashChange: nil,
  318. waitForPodsWithLabel: errors.New("boo! failed"),
  319. },
  320. moveFileFunc: func(oldPath, newPath string) error {
  321. return os.Rename(oldPath, newPath)
  322. },
  323. expectedErr: true,
  324. manifestShouldChange: false,
  325. },
  326. {
  327. description: "any path-moving error should result in a rollback and an abort",
  328. waitErrsToReturn: map[string]error{
  329. waitForHashes: nil,
  330. waitForHashChange: nil,
  331. waitForPodsWithLabel: nil,
  332. },
  333. moveFileFunc: func(oldPath, newPath string) error {
  334. // fail for kube-apiserver move
  335. if strings.Contains(newPath, "kube-apiserver") {
  336. return errors.New("moving the kube-apiserver file failed")
  337. }
  338. return os.Rename(oldPath, newPath)
  339. },
  340. expectedErr: true,
  341. manifestShouldChange: false,
  342. },
  343. {
  344. description: "any path-moving error should result in a rollback and an abort",
  345. waitErrsToReturn: map[string]error{
  346. waitForHashes: nil,
  347. waitForHashChange: nil,
  348. waitForPodsWithLabel: nil,
  349. },
  350. moveFileFunc: func(oldPath, newPath string) error {
  351. // fail for kube-controller-manager move
  352. if strings.Contains(newPath, "kube-controller-manager") {
  353. return errors.New("moving the kube-apiserver file failed")
  354. }
  355. return os.Rename(oldPath, newPath)
  356. },
  357. expectedErr: true,
  358. manifestShouldChange: false,
  359. },
  360. {
  361. description: "any path-moving error should result in a rollback and an abort; even though this is the last component (kube-apiserver and kube-controller-manager healthy)",
  362. waitErrsToReturn: map[string]error{
  363. waitForHashes: nil,
  364. waitForHashChange: nil,
  365. waitForPodsWithLabel: nil,
  366. },
  367. moveFileFunc: func(oldPath, newPath string) error {
  368. // fail for kube-scheduler move
  369. if strings.Contains(newPath, "kube-scheduler") {
  370. return errors.New("moving the kube-apiserver file failed")
  371. }
  372. return os.Rename(oldPath, newPath)
  373. },
  374. expectedErr: true,
  375. manifestShouldChange: false,
  376. },
  377. {
  378. description: "any cert renew error should result in a rollback and an abort; even though this is the last component (kube-apiserver and kube-controller-manager healthy)",
  379. waitErrsToReturn: map[string]error{
  380. waitForHashes: nil,
  381. waitForHashChange: nil,
  382. waitForPodsWithLabel: nil,
  383. },
  384. moveFileFunc: func(oldPath, newPath string) error {
  385. return os.Rename(oldPath, newPath)
  386. },
  387. skipKubeConfig: kubeadmconstants.SchedulerKubeConfigFileName,
  388. expectedErr: true,
  389. manifestShouldChange: false,
  390. },
  391. {
  392. description: "any cert renew error should result in a rollback and an abort; even though this is admin.conf (kube-apiserver and kube-controller-manager and kube-scheduler healthy)",
  393. waitErrsToReturn: map[string]error{
  394. waitForHashes: nil,
  395. waitForHashChange: nil,
  396. waitForPodsWithLabel: nil,
  397. },
  398. moveFileFunc: func(oldPath, newPath string) error {
  399. return os.Rename(oldPath, newPath)
  400. },
  401. skipKubeConfig: kubeadmconstants.AdminKubeConfigFileName,
  402. expectedErr: true,
  403. manifestShouldChange: false,
  404. },
  405. }
  406. for _, rt := range tests {
  407. t.Run(rt.description, func(t *testing.T) {
  408. waiter := NewFakeStaticPodWaiter(rt.waitErrsToReturn)
  409. pathMgr, err := NewFakeStaticPodPathManager(rt.moveFileFunc)
  410. if err != nil {
  411. t.Fatalf("couldn't run NewFakeStaticPodPathManager: %v", err)
  412. }
  413. defer os.RemoveAll(pathMgr.(*fakeStaticPodPathManager).KubernetesDir())
  414. tmpKubernetesDir := pathMgr.(*fakeStaticPodPathManager).KubernetesDir()
  415. tempCertsDir, err := ioutil.TempDir("", "kubeadm-certs")
  416. if err != nil {
  417. t.Fatalf("couldn't create temporary certificates directory: %v", err)
  418. }
  419. defer os.RemoveAll(tempCertsDir)
  420. tmpEtcdDataDir, err := ioutil.TempDir("", "kubeadm-etcd-data")
  421. if err != nil {
  422. t.Fatalf("couldn't create temporary etcd data directory: %v", err)
  423. }
  424. defer os.RemoveAll(tmpEtcdDataDir)
  425. oldcfg, err := getConfig(constants.MinimumControlPlaneVersion.String(), tempCertsDir, tmpEtcdDataDir)
  426. if err != nil {
  427. t.Fatalf("couldn't create config: %v", err)
  428. }
  429. tree, err := certsphase.GetCertsWithoutEtcd().AsMap().CertTree()
  430. if err != nil {
  431. t.Fatalf("couldn't get cert tree: %v", err)
  432. }
  433. if err := tree.CreateTree(oldcfg); err != nil {
  434. t.Fatalf("couldn't get create cert tree: %v", err)
  435. }
  436. t.Logf("Wrote certs to %s\n", oldcfg.CertificatesDir)
  437. for _, kubeConfig := range []string{
  438. kubeadmconstants.AdminKubeConfigFileName,
  439. kubeadmconstants.SchedulerKubeConfigFileName,
  440. kubeadmconstants.ControllerManagerKubeConfigFileName,
  441. } {
  442. if rt.skipKubeConfig == kubeConfig {
  443. continue
  444. }
  445. if err := kubeconfigphase.CreateKubeConfigFile(kubeConfig, tmpKubernetesDir, oldcfg); err != nil {
  446. t.Fatalf("couldn't create kubeconfig %q: %v", kubeConfig, err)
  447. }
  448. }
  449. // Initialize the directory with v1.7 manifests; should then be upgraded to v1.8 using the method
  450. err = controlplanephase.CreateInitStaticPodManifestFiles(pathMgr.RealManifestDir(), oldcfg)
  451. if err != nil {
  452. t.Fatalf("couldn't run CreateInitStaticPodManifestFiles: %v", err)
  453. }
  454. err = etcdphase.CreateLocalEtcdStaticPodManifestFile(pathMgr.RealManifestDir(), oldcfg.NodeRegistration.Name, &oldcfg.ClusterConfiguration, &oldcfg.LocalAPIEndpoint)
  455. if err != nil {
  456. t.Fatalf("couldn't run CreateLocalEtcdStaticPodManifestFile: %v", err)
  457. }
  458. // Get a hash of the v1.7 API server manifest to compare later (was the file re-written)
  459. oldHash, err := getAPIServerHash(pathMgr.RealManifestDir())
  460. if err != nil {
  461. t.Fatalf("couldn't read temp file: %v", err)
  462. }
  463. newcfg, err := getConfig(constants.CurrentKubernetesVersion.String(), tempCertsDir, tmpEtcdDataDir)
  464. if err != nil {
  465. t.Fatalf("couldn't create config: %v", err)
  466. }
  467. // create the kubeadm etcd certs
  468. caCert, caKey, err := certsphase.KubeadmCertEtcdCA.CreateAsCA(newcfg)
  469. if err != nil {
  470. t.Fatalf("couldn't create new CA certificate: %v", err)
  471. }
  472. for _, cert := range []*certsphase.KubeadmCert{
  473. &certsphase.KubeadmCertEtcdServer,
  474. &certsphase.KubeadmCertEtcdPeer,
  475. &certsphase.KubeadmCertEtcdHealthcheck,
  476. &certsphase.KubeadmCertEtcdAPIClient,
  477. } {
  478. if err := cert.CreateFromCA(newcfg, caCert, caKey); err != nil {
  479. t.Fatalf("couldn't create certificate %s: %v", cert.Name, err)
  480. }
  481. }
  482. actualErr := StaticPodControlPlane(
  483. nil,
  484. waiter,
  485. pathMgr,
  486. newcfg,
  487. true,
  488. true,
  489. fakeTLSEtcdClient{
  490. TLS: false,
  491. },
  492. fakePodManifestEtcdClient{
  493. ManifestDir: pathMgr.RealManifestDir(),
  494. CertificatesDir: newcfg.CertificatesDir,
  495. },
  496. )
  497. if (actualErr != nil) != rt.expectedErr {
  498. t.Errorf(
  499. "failed UpgradeStaticPodControlPlane\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
  500. rt.description,
  501. rt.expectedErr,
  502. (actualErr != nil),
  503. actualErr,
  504. )
  505. }
  506. newHash, err := getAPIServerHash(pathMgr.RealManifestDir())
  507. if err != nil {
  508. t.Fatalf("couldn't read temp file: %v", err)
  509. }
  510. if (oldHash != newHash) != rt.manifestShouldChange {
  511. t.Errorf(
  512. "failed StaticPodControlPlane\n%s\n\texpected manifest change: %t\n\tgot: %t\n\tnewHash: %v",
  513. rt.description,
  514. rt.manifestShouldChange,
  515. (oldHash != newHash),
  516. newHash,
  517. )
  518. }
  519. })
  520. }
  521. }
  522. func getAPIServerHash(dir string) (string, error) {
  523. manifestPath := constants.GetStaticPodFilepath(constants.KubeAPIServer, dir)
  524. fileBytes, err := ioutil.ReadFile(manifestPath)
  525. if err != nil {
  526. return "", err
  527. }
  528. return fmt.Sprintf("%x", sha256.Sum256(fileBytes)), nil
  529. }
  530. func getConfig(version, certsDir, etcdDataDir string) (*kubeadmapi.InitConfiguration, error) {
  531. configBytes := []byte(fmt.Sprintf(testConfiguration, certsDir, etcdDataDir, version))
  532. // Unmarshal the config
  533. return configutil.BytesToInitConfiguration(configBytes)
  534. }
  535. func getTempDir(t *testing.T, name string) (string, func()) {
  536. dir, err := ioutil.TempDir(os.TempDir(), name)
  537. if err != nil {
  538. t.Fatalf("couldn't make temporary directory: %v", err)
  539. }
  540. return dir, func() {
  541. os.RemoveAll(dir)
  542. }
  543. }
  544. func TestCleanupDirs(t *testing.T) {
  545. tests := []struct {
  546. name string
  547. keepManifest, keepEtcd bool
  548. }{
  549. {
  550. name: "save manifest backup",
  551. keepManifest: true,
  552. },
  553. {
  554. name: "save both etcd and manifest",
  555. keepManifest: true,
  556. keepEtcd: true,
  557. },
  558. {
  559. name: "save nothing",
  560. },
  561. }
  562. for _, test := range tests {
  563. t.Run(test.name, func(t *testing.T) {
  564. realKubernetesDir, cleanup := getTempDir(t, "realKubernetesDir")
  565. defer cleanup()
  566. tempManifestDir, cleanup := getTempDir(t, "tempManifestDir")
  567. defer cleanup()
  568. backupManifestDir, cleanup := getTempDir(t, "backupManifestDir")
  569. defer cleanup()
  570. backupEtcdDir, cleanup := getTempDir(t, "backupEtcdDir")
  571. defer cleanup()
  572. mgr := NewKubeStaticPodPathManager(realKubernetesDir, tempManifestDir, backupManifestDir, backupEtcdDir, test.keepManifest, test.keepEtcd)
  573. err := mgr.CleanupDirs()
  574. if err != nil {
  575. t.Errorf("unexpected error cleaning up: %v", err)
  576. }
  577. if _, err := os.Stat(tempManifestDir); !os.IsNotExist(err) {
  578. t.Errorf("%q should not have existed", tempManifestDir)
  579. }
  580. _, err = os.Stat(backupManifestDir)
  581. if test.keepManifest {
  582. if err != nil {
  583. t.Errorf("unexpected error getting backup manifest dir")
  584. }
  585. } else {
  586. if !os.IsNotExist(err) {
  587. t.Error("expected backup manifest to not exist")
  588. }
  589. }
  590. _, err = os.Stat(backupEtcdDir)
  591. if test.keepEtcd {
  592. if err != nil {
  593. t.Errorf("unexpected error getting backup etcd dir")
  594. }
  595. } else {
  596. if !os.IsNotExist(err) {
  597. t.Error("expected backup etcd dir to not exist")
  598. }
  599. }
  600. })
  601. }
  602. }
  603. func TestRenewCertsByComponent(t *testing.T) {
  604. caCert, caKey := certstestutil.SetupCertificateAuthorithy(t)
  605. tests := []struct {
  606. name string
  607. component string
  608. externalCA bool
  609. externalFrontProxyCA bool
  610. skipCreateEtcdCA bool
  611. shouldErrorOnRenew bool
  612. certsShouldExist []*certsphase.KubeadmCert
  613. certsShouldBeRenewed []*certsphase.KubeadmCert // NB. If empty, it will assume certsShouldBeRenewed == certsShouldExist
  614. kubeConfigShouldExist []string
  615. }{
  616. {
  617. name: "all CA exist, all certs should be rotated for etcd",
  618. component: constants.Etcd,
  619. certsShouldExist: []*certsphase.KubeadmCert{
  620. &certsphase.KubeadmCertEtcdServer,
  621. &certsphase.KubeadmCertEtcdPeer,
  622. &certsphase.KubeadmCertEtcdHealthcheck,
  623. },
  624. },
  625. {
  626. name: "all CA exist, all certs should be rotated for apiserver",
  627. component: constants.KubeAPIServer,
  628. certsShouldExist: []*certsphase.KubeadmCert{
  629. &certsphase.KubeadmCertEtcdAPIClient,
  630. &certsphase.KubeadmCertAPIServer,
  631. &certsphase.KubeadmCertKubeletClient,
  632. &certsphase.KubeadmCertFrontProxyClient,
  633. },
  634. },
  635. {
  636. name: "external CA, renew only certificates not signed by CA for apiserver",
  637. component: constants.KubeAPIServer,
  638. certsShouldExist: []*certsphase.KubeadmCert{
  639. &certsphase.KubeadmCertEtcdAPIClient,
  640. &certsphase.KubeadmCertFrontProxyClient,
  641. &certsphase.KubeadmCertAPIServer,
  642. &certsphase.KubeadmCertKubeletClient,
  643. },
  644. certsShouldBeRenewed: []*certsphase.KubeadmCert{
  645. &certsphase.KubeadmCertEtcdAPIClient,
  646. &certsphase.KubeadmCertFrontProxyClient,
  647. },
  648. externalCA: true,
  649. },
  650. {
  651. name: "external front-proxy-CA, renew only certificates not signed by front-proxy-CA for apiserver",
  652. component: constants.KubeAPIServer,
  653. certsShouldExist: []*certsphase.KubeadmCert{
  654. &certsphase.KubeadmCertEtcdAPIClient,
  655. &certsphase.KubeadmCertFrontProxyClient,
  656. &certsphase.KubeadmCertAPIServer,
  657. &certsphase.KubeadmCertKubeletClient,
  658. },
  659. certsShouldBeRenewed: []*certsphase.KubeadmCert{
  660. &certsphase.KubeadmCertEtcdAPIClient,
  661. &certsphase.KubeadmCertAPIServer,
  662. &certsphase.KubeadmCertKubeletClient,
  663. },
  664. externalFrontProxyCA: true,
  665. },
  666. {
  667. name: "all CA exist, should be rotated for scheduler",
  668. component: constants.KubeScheduler,
  669. kubeConfigShouldExist: []string{
  670. kubeadmconstants.SchedulerKubeConfigFileName,
  671. },
  672. },
  673. {
  674. name: "all CA exist, should be rotated for controller manager",
  675. component: constants.KubeControllerManager,
  676. kubeConfigShouldExist: []string{
  677. kubeadmconstants.ControllerManagerKubeConfigFileName,
  678. },
  679. },
  680. {
  681. name: "missing a cert to renew",
  682. component: constants.Etcd,
  683. shouldErrorOnRenew: true,
  684. certsShouldExist: []*certsphase.KubeadmCert{
  685. &certsphase.KubeadmCertEtcdServer,
  686. &certsphase.KubeadmCertEtcdPeer,
  687. },
  688. },
  689. {
  690. name: "no CA, cannot continue",
  691. component: constants.Etcd,
  692. skipCreateEtcdCA: true,
  693. shouldErrorOnRenew: true,
  694. },
  695. }
  696. for _, test := range tests {
  697. t.Run(test.name, func(t *testing.T) {
  698. // Setup up basic requities
  699. tmpDir := testutil.SetupTempDir(t)
  700. defer os.RemoveAll(tmpDir)
  701. cfg := testutil.GetDefaultInternalConfig(t)
  702. cfg.CertificatesDir = tmpDir
  703. if err := pkiutil.WriteCertAndKey(tmpDir, constants.CACertAndKeyBaseName, caCert, caKey); err != nil {
  704. t.Fatalf("couldn't write out CA: %v", err)
  705. }
  706. if test.externalCA {
  707. os.Remove(filepath.Join(tmpDir, constants.CAKeyName))
  708. }
  709. if err := pkiutil.WriteCertAndKey(tmpDir, constants.FrontProxyCACertAndKeyBaseName, caCert, caKey); err != nil {
  710. t.Fatalf("couldn't write out front-proxy-CA: %v", err)
  711. }
  712. if test.externalFrontProxyCA {
  713. os.Remove(filepath.Join(tmpDir, constants.FrontProxyCAKeyName))
  714. }
  715. if !test.skipCreateEtcdCA {
  716. if err := pkiutil.WriteCertAndKey(tmpDir, constants.EtcdCACertAndKeyBaseName, caCert, caKey); err != nil {
  717. t.Fatalf("couldn't write out etcd-CA: %v", err)
  718. }
  719. }
  720. certMaps := make(map[string]big.Int)
  721. // Create expected certs and load to recorde the serial numbers
  722. for _, kubeCert := range test.certsShouldExist {
  723. if err := kubeCert.CreateFromCA(cfg, caCert, caKey); err != nil {
  724. t.Fatalf("couldn't create certificate %q: %v", kubeCert.Name, err)
  725. }
  726. cert, err := pkiutil.TryLoadCertFromDisk(tmpDir, kubeCert.BaseName)
  727. if err != nil {
  728. t.Fatalf("couldn't load certificate %q: %v", kubeCert.Name, err)
  729. }
  730. certMaps[kubeCert.Name] = *cert.SerialNumber
  731. }
  732. // Create expected kubeconfigs
  733. for _, kubeConfig := range test.kubeConfigShouldExist {
  734. if err := kubeconfigphase.CreateKubeConfigFile(kubeConfig, tmpDir, cfg); err != nil {
  735. t.Fatalf("couldn't create kubeconfig %q: %v", kubeConfig, err)
  736. }
  737. newCerts, err := getEmbeddedCerts(tmpDir, kubeConfig)
  738. if err != nil {
  739. t.Fatalf("error reading embedded certs from %s: %v", kubeConfig, err)
  740. }
  741. certMaps[kubeConfig] = *newCerts[0].SerialNumber
  742. }
  743. // Renew everything
  744. rm, err := renewal.NewManager(&cfg.ClusterConfiguration, tmpDir)
  745. if err != nil {
  746. t.Fatalf("Failed to create the certificate renewal manager: %v", err)
  747. }
  748. err = renewCertsByComponent(cfg, test.component, rm)
  749. if test.shouldErrorOnRenew {
  750. if err == nil {
  751. t.Fatal("expected renewal error, got nothing")
  752. }
  753. // expected error, got error
  754. return
  755. }
  756. if err != nil {
  757. t.Fatalf("couldn't renew certificates: %v", err)
  758. }
  759. // See if the certificate serial numbers change
  760. for _, kubeCert := range test.certsShouldExist {
  761. newCert, err := pkiutil.TryLoadCertFromDisk(tmpDir, kubeCert.BaseName)
  762. if err != nil {
  763. t.Errorf("couldn't load new certificate %q: %v", kubeCert.Name, err)
  764. continue
  765. }
  766. oldSerial, _ := certMaps[kubeCert.Name]
  767. shouldBeRenewed := true
  768. if test.certsShouldBeRenewed != nil {
  769. shouldBeRenewed = false
  770. for _, x := range test.certsShouldBeRenewed {
  771. if x.Name == kubeCert.Name {
  772. shouldBeRenewed = true
  773. }
  774. }
  775. }
  776. if shouldBeRenewed && oldSerial.Cmp(newCert.SerialNumber) == 0 {
  777. t.Errorf("certifitate %v was not reissued when expected", kubeCert.Name)
  778. }
  779. if !shouldBeRenewed && oldSerial.Cmp(newCert.SerialNumber) != 0 {
  780. t.Errorf("certifitate %v was reissued when not expected", kubeCert.Name)
  781. }
  782. }
  783. // See if the embedded certificate serial numbers change
  784. for _, kubeConfig := range test.kubeConfigShouldExist {
  785. newCerts, err := getEmbeddedCerts(tmpDir, kubeConfig)
  786. if err != nil {
  787. t.Fatalf("error reading embedded certs from %s: %v", kubeConfig, err)
  788. }
  789. oldSerial, _ := certMaps[kubeConfig]
  790. if oldSerial.Cmp(newCerts[0].SerialNumber) == 0 {
  791. t.Errorf("certifitate %v was not reissued", kubeConfig)
  792. }
  793. }
  794. })
  795. }
  796. }
  797. func getEmbeddedCerts(tmpDir, kubeConfig string) ([]*x509.Certificate, error) {
  798. kubeconfigPath := filepath.Join(tmpDir, kubeConfig)
  799. newConfig, err := clientcmd.LoadFromFile(kubeconfigPath)
  800. if err != nil {
  801. return nil, errors.Wrapf(err, "failed to load kubeconfig file %s", kubeconfigPath)
  802. }
  803. authInfoName := newConfig.Contexts[newConfig.CurrentContext].AuthInfo
  804. authInfo := newConfig.AuthInfos[authInfoName]
  805. return certutil.ParseCertsPEM(authInfo.ClientCertificateData)
  806. }
  807. func TestGetPathManagerForUpgrade(t *testing.T) {
  808. haEtcd := &kubeadmapi.InitConfiguration{
  809. ClusterConfiguration: kubeadmapi.ClusterConfiguration{
  810. Etcd: kubeadmapi.Etcd{
  811. External: &kubeadmapi.ExternalEtcd{
  812. Endpoints: []string{"10.100.0.1:2379", "10.100.0.2:2379", "10.100.0.3:2379"},
  813. },
  814. },
  815. },
  816. }
  817. noHAEtcd := &kubeadmapi.InitConfiguration{}
  818. tests := []struct {
  819. name string
  820. cfg *kubeadmapi.InitConfiguration
  821. etcdUpgrade bool
  822. shouldDeleteEtcd bool
  823. }{
  824. {
  825. name: "ha etcd but no etcd upgrade",
  826. cfg: haEtcd,
  827. etcdUpgrade: false,
  828. shouldDeleteEtcd: true,
  829. },
  830. {
  831. name: "non-ha etcd with etcd upgrade",
  832. cfg: noHAEtcd,
  833. etcdUpgrade: true,
  834. shouldDeleteEtcd: false,
  835. },
  836. {
  837. name: "ha etcd and etcd upgrade",
  838. cfg: haEtcd,
  839. etcdUpgrade: true,
  840. shouldDeleteEtcd: true,
  841. },
  842. }
  843. for _, test := range tests {
  844. t.Run(test.name, func(t *testing.T) {
  845. // Use a temporary directory
  846. tmpdir, err := ioutil.TempDir("", "TestGetPathManagerForUpgrade")
  847. if err != nil {
  848. t.Fatalf("unexpected error making temporary directory: %v", err)
  849. }
  850. defer func() {
  851. os.RemoveAll(tmpdir)
  852. }()
  853. pathmgr, err := GetPathManagerForUpgrade(tmpdir, test.cfg, test.etcdUpgrade)
  854. if err != nil {
  855. t.Fatalf("unexpected error creating path manager: %v", err)
  856. }
  857. if _, err := os.Stat(pathmgr.BackupManifestDir()); os.IsNotExist(err) {
  858. t.Errorf("expected manifest dir %s to exist, but it did not (%v)", pathmgr.BackupManifestDir(), err)
  859. }
  860. if _, err := os.Stat(pathmgr.BackupEtcdDir()); os.IsNotExist(err) {
  861. t.Errorf("expected etcd dir %s to exist, but it did not (%v)", pathmgr.BackupEtcdDir(), err)
  862. }
  863. if err := pathmgr.CleanupDirs(); err != nil {
  864. t.Fatalf("unexpected error cleaning up directories: %v", err)
  865. }
  866. if _, err := os.Stat(pathmgr.BackupManifestDir()); os.IsNotExist(err) {
  867. t.Errorf("expected manifest dir %s to exist, but it did not (%v)", pathmgr.BackupManifestDir(), err)
  868. }
  869. if test.shouldDeleteEtcd {
  870. if _, err := os.Stat(pathmgr.BackupEtcdDir()); !os.IsNotExist(err) {
  871. t.Errorf("expected etcd dir %s not to exist, but it did (%v)", pathmgr.BackupEtcdDir(), err)
  872. }
  873. } else {
  874. if _, err := os.Stat(pathmgr.BackupEtcdDir()); os.IsNotExist(err) {
  875. t.Errorf("expected etcd dir %s to exist, but it did not", pathmgr.BackupEtcdDir())
  876. }
  877. }
  878. })
  879. }
  880. }