k8s.io/kubernetes@v1.29.3/test/e2e/upgrades/apps/deployments.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package apps
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	appsv1 "k8s.io/api/apps/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/apimachinery/pkg/util/wait"
    28  	clientset "k8s.io/client-go/kubernetes"
    29  	deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
    30  	"k8s.io/kubernetes/test/e2e/framework"
    31  	e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
    32  	"k8s.io/kubernetes/test/e2e/upgrades"
    33  
    34  	"github.com/onsi/ginkgo/v2"
    35  	"github.com/onsi/gomega"
    36  
    37  	imageutils "k8s.io/kubernetes/test/utils/image"
    38  )
    39  
    40  const (
    41  	deploymentName = "dp"
    42  	// poll is how often to poll pods, nodes and claims.
    43  	poll            = 2 * time.Second
    44  	pollLongTimeout = 5 * time.Minute
    45  )
    46  
    47  // TODO: Test that the deployment stays available during master (and maybe
    48  // node and cluster upgrades).
    49  
    50  // DeploymentUpgradeTest tests that a deployment is using the same replica
    51  // sets before and after a cluster upgrade.
    52  type DeploymentUpgradeTest struct {
    53  	oldDeploymentUID types.UID
    54  	oldRSUID         types.UID
    55  	newRSUID         types.UID
    56  }
    57  
    58  // Name returns the tracking name of the test.
    59  func (DeploymentUpgradeTest) Name() string { return "[sig-apps] deployment-upgrade" }
    60  
    61  // Setup creates a deployment and makes sure it has a new and an old replicaset running.
    62  func (t *DeploymentUpgradeTest) Setup(ctx context.Context, f *framework.Framework) {
    63  	c := f.ClientSet
    64  	nginxImage := imageutils.GetE2EImage(imageutils.Nginx)
    65  
    66  	ns := f.Namespace.Name
    67  	deploymentClient := c.AppsV1().Deployments(ns)
    68  	rsClient := c.AppsV1().ReplicaSets(ns)
    69  
    70  	ginkgo.By(fmt.Sprintf("Creating a deployment %q with 1 replica in namespace %q", deploymentName, ns))
    71  	d := e2edeployment.NewDeployment(deploymentName, int32(1), map[string]string{"test": "upgrade"}, "nginx", nginxImage, appsv1.RollingUpdateDeploymentStrategyType)
    72  	deployment, err := deploymentClient.Create(ctx, d, metav1.CreateOptions{})
    73  	framework.ExpectNoError(err)
    74  
    75  	ginkgo.By(fmt.Sprintf("Waiting deployment %q to complete", deploymentName))
    76  	framework.ExpectNoError(e2edeployment.WaitForDeploymentComplete(c, deployment))
    77  
    78  	ginkgo.By(fmt.Sprintf("Getting replicaset revision 1 of deployment %q", deploymentName))
    79  	rsSelector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector)
    80  	framework.ExpectNoError(err)
    81  	rsList, err := rsClient.List(ctx, metav1.ListOptions{LabelSelector: rsSelector.String()})
    82  	framework.ExpectNoError(err)
    83  	rss := rsList.Items
    84  	gomega.Expect(rss).To(gomega.HaveLen(1), "expected one replicaset, got %d", len(rss))
    85  	t.oldRSUID = rss[0].UID
    86  
    87  	ginkgo.By(fmt.Sprintf("Waiting for revision of the deployment %q to become 1", deploymentName))
    88  	framework.ExpectNoError(waitForDeploymentRevision(ctx, c, deployment, "1"))
    89  
    90  	// Trigger a new rollout so that we have some history.
    91  	ginkgo.By(fmt.Sprintf("Triggering a new rollout for deployment %q", deploymentName))
    92  	deployment, err = e2edeployment.UpdateDeploymentWithRetries(c, ns, deploymentName, func(update *appsv1.Deployment) {
    93  		update.Spec.Template.Spec.Containers[0].Name = "updated-name"
    94  	})
    95  	framework.ExpectNoError(err)
    96  
    97  	ginkgo.By(fmt.Sprintf("Waiting deployment %q to complete", deploymentName))
    98  	framework.ExpectNoError(e2edeployment.WaitForDeploymentComplete(c, deployment))
    99  
   100  	ginkgo.By(fmt.Sprintf("Getting replicasets revision 1 and 2 of deployment %q", deploymentName))
   101  	rsList, err = rsClient.List(ctx, metav1.ListOptions{LabelSelector: rsSelector.String()})
   102  	framework.ExpectNoError(err)
   103  	rss = rsList.Items
   104  	gomega.Expect(rss).To(gomega.HaveLen(2), "expected 2 replicaset, got %d", len(rss))
   105  
   106  	ginkgo.By(fmt.Sprintf("Checking replicaset of deployment %q that is created before rollout survives the rollout", deploymentName))
   107  	switch t.oldRSUID {
   108  	case rss[0].UID:
   109  		t.newRSUID = rss[1].UID
   110  	case rss[1].UID:
   111  		t.newRSUID = rss[0].UID
   112  	default:
   113  		framework.ExpectNoError(fmt.Errorf("old replicaset with UID %q does not survive rollout", t.oldRSUID))
   114  	}
   115  
   116  	ginkgo.By(fmt.Sprintf("Waiting for revision of the deployment %q to become 2", deploymentName))
   117  	framework.ExpectNoError(waitForDeploymentRevision(ctx, c, deployment, "2"))
   118  
   119  	t.oldDeploymentUID = deployment.UID
   120  }
   121  
   122  // Test checks whether the replicasets for a deployment are the same after an upgrade.
   123  func (t *DeploymentUpgradeTest) Test(ctx context.Context, f *framework.Framework, done <-chan struct{}, upgrade upgrades.UpgradeType) {
   124  	// Block until upgrade is done
   125  	ginkgo.By(fmt.Sprintf("Waiting for upgrade to finish before checking replicasets for deployment %q", deploymentName))
   126  	<-done
   127  
   128  	c := f.ClientSet
   129  	ns := f.Namespace.Name
   130  	deploymentClient := c.AppsV1().Deployments(ns)
   131  	rsClient := c.AppsV1().ReplicaSets(ns)
   132  
   133  	deployment, err := deploymentClient.Get(ctx, deploymentName, metav1.GetOptions{})
   134  	framework.ExpectNoError(err)
   135  
   136  	ginkgo.By(fmt.Sprintf("Checking UID to verify deployment %q survives upgrade", deploymentName))
   137  	gomega.Expect(deployment.UID).To(gomega.Equal(t.oldDeploymentUID))
   138  
   139  	ginkgo.By(fmt.Sprintf("Verifying deployment %q does not create new replicasets", deploymentName))
   140  	rsSelector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
   141  	framework.ExpectNoError(err)
   142  	rsList, err := rsClient.List(ctx, metav1.ListOptions{LabelSelector: rsSelector.String()})
   143  	framework.ExpectNoError(err)
   144  	rss := rsList.Items
   145  	gomega.Expect(rss).To(gomega.HaveLen(2), "expected 2 replicaset, got %d", len(rss))
   146  
   147  	switch t.oldRSUID {
   148  	case rss[0].UID:
   149  		gomega.Expect(rss[1].UID).To(gomega.Equal(t.newRSUID))
   150  	case rss[1].UID:
   151  		gomega.Expect(rss[0].UID).To(gomega.Equal(t.newRSUID))
   152  	default:
   153  		framework.ExpectNoError(fmt.Errorf("new replicasets are created during upgrade of deployment %q", deploymentName))
   154  	}
   155  
   156  	ginkgo.By(fmt.Sprintf("Verifying revision of the deployment %q is still 2", deploymentName))
   157  	gomega.Expect(deployment.Annotations).To(gomega.HaveKeyWithValue(deploymentutil.RevisionAnnotation, "2"))
   158  
   159  	ginkgo.By(fmt.Sprintf("Waiting for deployment %q to complete adoption", deploymentName))
   160  	framework.ExpectNoError(e2edeployment.WaitForDeploymentComplete(c, deployment))
   161  
   162  	// Verify the upgraded deployment is active by scaling up the deployment by 1
   163  	ginkgo.By(fmt.Sprintf("Scaling up replicaset of deployment %q by 1", deploymentName))
   164  	deploymentWithUpdatedReplicas, err := e2edeployment.UpdateDeploymentWithRetries(c, ns, deploymentName, func(deployment *appsv1.Deployment) {
   165  		*deployment.Spec.Replicas = *deployment.Spec.Replicas + 1
   166  	})
   167  	framework.ExpectNoError(err)
   168  
   169  	ginkgo.By(fmt.Sprintf("Waiting for deployment %q to complete after scaling", deploymentName))
   170  	framework.ExpectNoError(e2edeployment.WaitForDeploymentComplete(c, deploymentWithUpdatedReplicas))
   171  }
   172  
   173  // Teardown cleans up any remaining resources.
   174  func (t *DeploymentUpgradeTest) Teardown(ctx context.Context, f *framework.Framework) {
   175  	// rely on the namespace deletion to clean up everything
   176  }
   177  
   178  // waitForDeploymentRevision waits for becoming the target revision of a delopyment.
   179  func waitForDeploymentRevision(ctx context.Context, c clientset.Interface, d *appsv1.Deployment, targetRevision string) error {
   180  	err := wait.PollImmediate(poll, pollLongTimeout, func() (bool, error) {
   181  		deployment, err := c.AppsV1().Deployments(d.Namespace).Get(ctx, d.Name, metav1.GetOptions{})
   182  		if err != nil {
   183  			return false, err
   184  		}
   185  		revision := deployment.Annotations[deploymentutil.RevisionAnnotation]
   186  		return revision == targetRevision, nil
   187  	})
   188  	if err != nil {
   189  		return fmt.Errorf("error waiting for revision to become %q for deployment %q: %w", targetRevision, d.Name, err)
   190  	}
   191  	return nil
   192  }