util.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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 deployment
  14. import (
  15. "context"
  16. "fmt"
  17. "net/http/httptest"
  18. "sync"
  19. "testing"
  20. "time"
  21. apps "k8s.io/api/apps/v1"
  22. "k8s.io/api/core/v1"
  23. extensions "k8s.io/api/extensions/v1beta1"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. "k8s.io/apimachinery/pkg/util/intstr"
  26. "k8s.io/apimachinery/pkg/util/wait"
  27. "k8s.io/client-go/informers"
  28. clientset "k8s.io/client-go/kubernetes"
  29. restclient "k8s.io/client-go/rest"
  30. podutil "k8s.io/kubernetes/pkg/api/v1/pod"
  31. "k8s.io/kubernetes/pkg/controller/deployment"
  32. deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
  33. "k8s.io/kubernetes/pkg/controller/replicaset"
  34. "k8s.io/kubernetes/test/integration/framework"
  35. testutil "k8s.io/kubernetes/test/utils"
  36. )
  37. const (
  38. pollInterval = 100 * time.Millisecond
  39. pollTimeout = 60 * time.Second
  40. fakeContainerName = "fake-name"
  41. fakeImage = "fakeimage"
  42. )
  43. var pauseFn = func(update *apps.Deployment) {
  44. update.Spec.Paused = true
  45. }
  46. var resumeFn = func(update *apps.Deployment) {
  47. update.Spec.Paused = false
  48. }
  49. type deploymentTester struct {
  50. t *testing.T
  51. c clientset.Interface
  52. deployment *apps.Deployment
  53. }
  54. func testLabels() map[string]string {
  55. return map[string]string{"name": "test"}
  56. }
  57. // newDeployment returns a RollingUpdate Deployment with a fake container image
  58. func newDeployment(name, ns string, replicas int32) *apps.Deployment {
  59. return &apps.Deployment{
  60. TypeMeta: metav1.TypeMeta{
  61. Kind: "Deployment",
  62. APIVersion: "apps/v1",
  63. },
  64. ObjectMeta: metav1.ObjectMeta{
  65. Namespace: ns,
  66. Name: name,
  67. },
  68. Spec: apps.DeploymentSpec{
  69. Replicas: &replicas,
  70. Selector: &metav1.LabelSelector{MatchLabels: testLabels()},
  71. Strategy: apps.DeploymentStrategy{
  72. Type: apps.RollingUpdateDeploymentStrategyType,
  73. RollingUpdate: new(apps.RollingUpdateDeployment),
  74. },
  75. Template: v1.PodTemplateSpec{
  76. ObjectMeta: metav1.ObjectMeta{
  77. Labels: testLabels(),
  78. },
  79. Spec: v1.PodSpec{
  80. Containers: []v1.Container{
  81. {
  82. Name: fakeContainerName,
  83. Image: fakeImage,
  84. },
  85. },
  86. },
  87. },
  88. },
  89. }
  90. }
  91. func newReplicaSet(name, ns string, replicas int32) *apps.ReplicaSet {
  92. return &apps.ReplicaSet{
  93. TypeMeta: metav1.TypeMeta{
  94. Kind: "ReplicaSet",
  95. APIVersion: "apps/v1",
  96. },
  97. ObjectMeta: metav1.ObjectMeta{
  98. Namespace: ns,
  99. Name: name,
  100. Labels: testLabels(),
  101. },
  102. Spec: apps.ReplicaSetSpec{
  103. Selector: &metav1.LabelSelector{
  104. MatchLabels: testLabels(),
  105. },
  106. Replicas: &replicas,
  107. Template: v1.PodTemplateSpec{
  108. ObjectMeta: metav1.ObjectMeta{
  109. Labels: testLabels(),
  110. },
  111. Spec: v1.PodSpec{
  112. Containers: []v1.Container{
  113. {
  114. Name: fakeContainerName,
  115. Image: fakeImage,
  116. },
  117. },
  118. },
  119. },
  120. },
  121. }
  122. }
  123. func newDeploymentRollback(name string, annotations map[string]string, revision int64) *extensions.DeploymentRollback {
  124. return &extensions.DeploymentRollback{
  125. Name: name,
  126. UpdatedAnnotations: annotations,
  127. RollbackTo: extensions.RollbackConfig{Revision: revision},
  128. }
  129. }
  130. // dcSetup sets up necessities for Deployment integration test, including master, apiserver, informers, and clientset
  131. func dcSetup(t *testing.T) (*httptest.Server, framework.CloseFunc, *replicaset.ReplicaSetController, *deployment.DeploymentController, informers.SharedInformerFactory, clientset.Interface) {
  132. masterConfig := framework.NewIntegrationTestMasterConfig()
  133. _, s, closeFn := framework.RunAMaster(masterConfig)
  134. config := restclient.Config{Host: s.URL}
  135. clientSet, err := clientset.NewForConfig(&config)
  136. if err != nil {
  137. t.Fatalf("error in create clientset: %v", err)
  138. }
  139. resyncPeriod := 12 * time.Hour
  140. informers := informers.NewSharedInformerFactory(clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "deployment-informers")), resyncPeriod)
  141. dc, err := deployment.NewDeploymentController(
  142. informers.Apps().V1().Deployments(),
  143. informers.Apps().V1().ReplicaSets(),
  144. informers.Core().V1().Pods(),
  145. clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "deployment-controller")),
  146. )
  147. if err != nil {
  148. t.Fatalf("error creating Deployment controller: %v", err)
  149. }
  150. rm := replicaset.NewReplicaSetController(
  151. informers.Apps().V1().ReplicaSets(),
  152. informers.Core().V1().Pods(),
  153. clientset.NewForConfigOrDie(restclient.AddUserAgent(&config, "replicaset-controller")),
  154. replicaset.BurstReplicas,
  155. )
  156. return s, closeFn, rm, dc, informers, clientSet
  157. }
  158. // dcSimpleSetup sets up necessities for Deployment integration test, including master, apiserver,
  159. // and clientset, but not controllers and informers
  160. func dcSimpleSetup(t *testing.T) (*httptest.Server, framework.CloseFunc, clientset.Interface) {
  161. masterConfig := framework.NewIntegrationTestMasterConfig()
  162. _, s, closeFn := framework.RunAMaster(masterConfig)
  163. config := restclient.Config{Host: s.URL}
  164. clientSet, err := clientset.NewForConfig(&config)
  165. if err != nil {
  166. t.Fatalf("error in create clientset: %v", err)
  167. }
  168. return s, closeFn, clientSet
  169. }
  170. // addPodConditionReady sets given pod status to ready at given time
  171. func addPodConditionReady(pod *v1.Pod, time metav1.Time) {
  172. pod.Status = v1.PodStatus{
  173. Phase: v1.PodRunning,
  174. Conditions: []v1.PodCondition{
  175. {
  176. Type: v1.PodReady,
  177. Status: v1.ConditionTrue,
  178. LastTransitionTime: time,
  179. },
  180. },
  181. }
  182. }
  183. func (d *deploymentTester) waitForDeploymentRevisionAndImage(revision, image string) error {
  184. if err := testutil.WaitForDeploymentRevisionAndImage(d.c, d.deployment.Namespace, d.deployment.Name, revision, image, d.t.Logf, pollInterval, pollTimeout); err != nil {
  185. return fmt.Errorf("failed to wait for Deployment revision %s: %v", d.deployment.Name, err)
  186. }
  187. return nil
  188. }
  189. func markPodReady(c clientset.Interface, ns string, pod *v1.Pod) error {
  190. addPodConditionReady(pod, metav1.Now())
  191. _, err := c.CoreV1().Pods(ns).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{})
  192. return err
  193. }
  194. func intOrStrP(num int) *intstr.IntOrString {
  195. intstr := intstr.FromInt(num)
  196. return &intstr
  197. }
  198. // markUpdatedPodsReady manually marks updated Deployment pods status to ready,
  199. // until the deployment is complete
  200. func (d *deploymentTester) markUpdatedPodsReady(wg *sync.WaitGroup) {
  201. defer wg.Done()
  202. ns := d.deployment.Namespace
  203. err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
  204. // We're done when the deployment is complete
  205. if completed, err := d.deploymentComplete(); err != nil {
  206. return false, err
  207. } else if completed {
  208. return true, nil
  209. }
  210. // Otherwise, mark remaining pods as ready
  211. pods, err := d.listUpdatedPods()
  212. if err != nil {
  213. d.t.Log(err)
  214. return false, nil
  215. }
  216. d.t.Logf("%d/%d of deployment pods are created", len(pods), *d.deployment.Spec.Replicas)
  217. for i := range pods {
  218. pod := pods[i]
  219. if podutil.IsPodReady(&pod) {
  220. continue
  221. }
  222. if err = markPodReady(d.c, ns, &pod); err != nil {
  223. d.t.Logf("failed to update Deployment pod %s, will retry later: %v", pod.Name, err)
  224. }
  225. }
  226. return false, nil
  227. })
  228. if err != nil {
  229. d.t.Fatalf("failed to mark updated Deployment pods to ready: %v", err)
  230. }
  231. }
  232. func (d *deploymentTester) deploymentComplete() (bool, error) {
  233. latest, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(context.TODO(), d.deployment.Name, metav1.GetOptions{})
  234. if err != nil {
  235. return false, err
  236. }
  237. return deploymentutil.DeploymentComplete(d.deployment, &latest.Status), nil
  238. }
  239. // Waits for the deployment to complete, and check rolling update strategy isn't broken at any times.
  240. // Rolling update strategy should not be broken during a rolling update.
  241. func (d *deploymentTester) waitForDeploymentCompleteAndCheckRolling() error {
  242. return testutil.WaitForDeploymentCompleteAndCheckRolling(d.c, d.deployment, d.t.Logf, pollInterval, pollTimeout)
  243. }
  244. // Waits for the deployment to complete, and don't check if rolling update strategy is broken.
  245. // Rolling update strategy is used only during a rolling update, and can be violated in other situations,
  246. // such as shortly after a scaling event or the deployment is just created.
  247. func (d *deploymentTester) waitForDeploymentComplete() error {
  248. return testutil.WaitForDeploymentComplete(d.c, d.deployment, d.t.Logf, pollInterval, pollTimeout)
  249. }
  250. // waitForDeploymentCompleteAndCheckRollingAndMarkPodsReady waits for the Deployment to complete
  251. // while marking updated Deployment pods as ready at the same time.
  252. // Uses hard check to make sure rolling update strategy is not violated at any times.
  253. func (d *deploymentTester) waitForDeploymentCompleteAndCheckRollingAndMarkPodsReady() error {
  254. var wg sync.WaitGroup
  255. // Manually mark updated Deployment pods as ready in a separate goroutine
  256. wg.Add(1)
  257. go d.markUpdatedPodsReady(&wg)
  258. // Wait for goroutine to finish, for all return paths.
  259. defer wg.Wait()
  260. // Wait for the Deployment status to complete while Deployment pods are becoming ready
  261. err := d.waitForDeploymentCompleteAndCheckRolling()
  262. if err != nil {
  263. return fmt.Errorf("failed to wait for Deployment %s to complete: %v", d.deployment.Name, err)
  264. }
  265. return nil
  266. }
  267. // waitForDeploymentCompleteAndMarkPodsReady waits for the Deployment to complete
  268. // while marking updated Deployment pods as ready at the same time.
  269. func (d *deploymentTester) waitForDeploymentCompleteAndMarkPodsReady() error {
  270. var wg sync.WaitGroup
  271. // Manually mark updated Deployment pods as ready in a separate goroutine
  272. wg.Add(1)
  273. go d.markUpdatedPodsReady(&wg)
  274. // Wait for the Deployment status to complete using soft check, while Deployment pods are becoming ready
  275. err := d.waitForDeploymentComplete()
  276. if err != nil {
  277. return fmt.Errorf("failed to wait for Deployment status %s: %v", d.deployment.Name, err)
  278. }
  279. // Wait for goroutine to finish
  280. wg.Wait()
  281. return nil
  282. }
  283. func (d *deploymentTester) updateDeployment(applyUpdate testutil.UpdateDeploymentFunc) (*apps.Deployment, error) {
  284. return testutil.UpdateDeploymentWithRetries(d.c, d.deployment.Namespace, d.deployment.Name, applyUpdate, d.t.Logf, pollInterval, pollTimeout)
  285. }
  286. func (d *deploymentTester) waitForObservedDeployment(desiredGeneration int64) error {
  287. if err := testutil.WaitForObservedDeployment(d.c, d.deployment.Namespace, d.deployment.Name, desiredGeneration); err != nil {
  288. return fmt.Errorf("failed waiting for ObservedGeneration of deployment %s to become %d: %v", d.deployment.Name, desiredGeneration, err)
  289. }
  290. return nil
  291. }
  292. func (d *deploymentTester) getNewReplicaSet() (*apps.ReplicaSet, error) {
  293. deployment, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(context.TODO(), d.deployment.Name, metav1.GetOptions{})
  294. if err != nil {
  295. return nil, fmt.Errorf("failed retrieving deployment %s: %v", d.deployment.Name, err)
  296. }
  297. rs, err := deploymentutil.GetNewReplicaSet(deployment, d.c.AppsV1())
  298. if err != nil {
  299. return nil, fmt.Errorf("failed retrieving new replicaset of deployment %s: %v", d.deployment.Name, err)
  300. }
  301. return rs, nil
  302. }
  303. func (d *deploymentTester) expectNoNewReplicaSet() error {
  304. rs, err := d.getNewReplicaSet()
  305. if err != nil {
  306. return err
  307. }
  308. if rs != nil {
  309. return fmt.Errorf("expected deployment %s not to create a new replicaset, got %v", d.deployment.Name, rs)
  310. }
  311. return nil
  312. }
  313. func (d *deploymentTester) expectNewReplicaSet() (*apps.ReplicaSet, error) {
  314. rs, err := d.getNewReplicaSet()
  315. if err != nil {
  316. return nil, err
  317. }
  318. if rs == nil {
  319. return nil, fmt.Errorf("expected deployment %s to create a new replicaset, got nil", d.deployment.Name)
  320. }
  321. return rs, nil
  322. }
  323. func (d *deploymentTester) updateReplicaSet(name string, applyUpdate testutil.UpdateReplicaSetFunc) (*apps.ReplicaSet, error) {
  324. return testutil.UpdateReplicaSetWithRetries(d.c, d.deployment.Namespace, name, applyUpdate, d.t.Logf, pollInterval, pollTimeout)
  325. }
  326. func (d *deploymentTester) updateReplicaSetStatus(name string, applyStatusUpdate testutil.UpdateReplicaSetFunc) (*apps.ReplicaSet, error) {
  327. return testutil.UpdateReplicaSetStatusWithRetries(d.c, d.deployment.Namespace, name, applyStatusUpdate, d.t.Logf, pollInterval, pollTimeout)
  328. }
  329. // waitForDeploymentRollbackCleared waits for deployment either started rolling back or doesn't need to rollback.
  330. func (d *deploymentTester) waitForDeploymentRollbackCleared() error {
  331. return testutil.WaitForDeploymentRollbackCleared(d.c, d.deployment.Namespace, d.deployment.Name, pollInterval, pollTimeout)
  332. }
  333. // checkDeploymentRevisionAndImage checks if the input deployment's and its new replica set's revision and image are as expected.
  334. func (d *deploymentTester) checkDeploymentRevisionAndImage(revision, image string) error {
  335. return testutil.CheckDeploymentRevisionAndImage(d.c, d.deployment.Namespace, d.deployment.Name, revision, image)
  336. }
  337. func (d *deploymentTester) waitForDeploymentUpdatedReplicasGTE(minUpdatedReplicas int32) error {
  338. return testutil.WaitForDeploymentUpdatedReplicasGTE(d.c, d.deployment.Namespace, d.deployment.Name, minUpdatedReplicas, d.deployment.Generation, pollInterval, pollTimeout)
  339. }
  340. func (d *deploymentTester) waitForDeploymentWithCondition(reason string, condType apps.DeploymentConditionType) error {
  341. return testutil.WaitForDeploymentWithCondition(d.c, d.deployment.Namespace, d.deployment.Name, reason, condType, d.t.Logf, pollInterval, pollTimeout)
  342. }
  343. func (d *deploymentTester) listUpdatedPods() ([]v1.Pod, error) {
  344. selector, err := metav1.LabelSelectorAsSelector(d.deployment.Spec.Selector)
  345. if err != nil {
  346. return nil, fmt.Errorf("failed to parse deployment selector: %v", err)
  347. }
  348. pods, err := d.c.CoreV1().Pods(d.deployment.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()})
  349. if err != nil {
  350. return nil, fmt.Errorf("failed to list deployment pods, will retry later: %v", err)
  351. }
  352. newRS, err := d.getNewReplicaSet()
  353. if err != nil {
  354. return nil, fmt.Errorf("failed to get new replicaset of deployment %q: %v", d.deployment.Name, err)
  355. }
  356. if newRS == nil {
  357. return nil, fmt.Errorf("unable to find new replicaset of deployment %q", d.deployment.Name)
  358. }
  359. var ownedPods []v1.Pod
  360. for _, pod := range pods.Items {
  361. rs := metav1.GetControllerOf(&pod)
  362. if rs.UID == newRS.UID {
  363. ownedPods = append(ownedPods, pod)
  364. }
  365. }
  366. return ownedPods, nil
  367. }
  368. func (d *deploymentTester) waitRSStable(replicaset *apps.ReplicaSet) error {
  369. return testutil.WaitRSStable(d.t, d.c, replicaset, pollInterval, pollTimeout)
  370. }
  371. func (d *deploymentTester) scaleDeployment(newReplicas int32) error {
  372. var err error
  373. d.deployment, err = d.updateDeployment(func(update *apps.Deployment) {
  374. update.Spec.Replicas = &newReplicas
  375. })
  376. if err != nil {
  377. return fmt.Errorf("failed updating deployment %q: %v", d.deployment.Name, err)
  378. }
  379. if err := d.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
  380. return err
  381. }
  382. rs, err := d.expectNewReplicaSet()
  383. if err != nil {
  384. return err
  385. }
  386. if *rs.Spec.Replicas != newReplicas {
  387. return fmt.Errorf("expected new replicaset replicas = %d, got %d", newReplicas, *rs.Spec.Replicas)
  388. }
  389. return nil
  390. }
  391. // waitForReadyReplicas waits for number of ready replicas to equal number of replicas.
  392. func (d *deploymentTester) waitForReadyReplicas() error {
  393. if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
  394. deployment, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(context.TODO(), d.deployment.Name, metav1.GetOptions{})
  395. if err != nil {
  396. return false, fmt.Errorf("failed to get deployment %q: %v", d.deployment.Name, err)
  397. }
  398. return deployment.Status.ReadyReplicas == *deployment.Spec.Replicas, nil
  399. }); err != nil {
  400. return fmt.Errorf("failed to wait for .readyReplicas to equal .replicas: %v", err)
  401. }
  402. return nil
  403. }
  404. // markUpdatedPodsReadyWithoutComplete marks updated Deployment pods as ready without waiting for deployment to complete.
  405. func (d *deploymentTester) markUpdatedPodsReadyWithoutComplete() error {
  406. if err := wait.PollImmediate(pollInterval, pollTimeout, func() (bool, error) {
  407. pods, err := d.listUpdatedPods()
  408. if err != nil {
  409. return false, err
  410. }
  411. for i := range pods {
  412. pod := pods[i]
  413. if podutil.IsPodReady(&pod) {
  414. continue
  415. }
  416. if err = markPodReady(d.c, d.deployment.Namespace, &pod); err != nil {
  417. d.t.Logf("failed to update Deployment pod %q, will retry later: %v", pod.Name, err)
  418. return false, nil
  419. }
  420. }
  421. return true, nil
  422. }); err != nil {
  423. return fmt.Errorf("failed to mark all updated pods as ready: %v", err)
  424. }
  425. return nil
  426. }
  427. // Verify all replicas fields of DeploymentStatus have desired count.
  428. // Immediately return an error when found a non-matching replicas field.
  429. func (d *deploymentTester) checkDeploymentStatusReplicasFields(replicas, updatedReplicas, readyReplicas, availableReplicas, unavailableReplicas int32) error {
  430. deployment, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(context.TODO(), d.deployment.Name, metav1.GetOptions{})
  431. if err != nil {
  432. return fmt.Errorf("failed to get deployment %q: %v", d.deployment.Name, err)
  433. }
  434. if deployment.Status.Replicas != replicas {
  435. return fmt.Errorf("unexpected .replicas: expect %d, got %d", replicas, deployment.Status.Replicas)
  436. }
  437. if deployment.Status.UpdatedReplicas != updatedReplicas {
  438. return fmt.Errorf("unexpected .updatedReplicas: expect %d, got %d", updatedReplicas, deployment.Status.UpdatedReplicas)
  439. }
  440. if deployment.Status.ReadyReplicas != readyReplicas {
  441. return fmt.Errorf("unexpected .readyReplicas: expect %d, got %d", readyReplicas, deployment.Status.ReadyReplicas)
  442. }
  443. if deployment.Status.AvailableReplicas != availableReplicas {
  444. return fmt.Errorf("unexpected .replicas: expect %d, got %d", availableReplicas, deployment.Status.AvailableReplicas)
  445. }
  446. if deployment.Status.UnavailableReplicas != unavailableReplicas {
  447. return fmt.Errorf("unexpected .replicas: expect %d, got %d", unavailableReplicas, deployment.Status.UnavailableReplicas)
  448. }
  449. return nil
  450. }