k8s.io/kubernetes@v1.29.3/test/e2e_node/mirror_pod_test.go (about)

     1  /*
     2  Copyright 2016 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 e2enode
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"time"
    25  
    26  	v1 "k8s.io/api/core/v1"
    27  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apimachinery/pkg/util/uuid"
    32  	"k8s.io/apimachinery/pkg/util/wait"
    33  	clientset "k8s.io/client-go/kubernetes"
    34  	kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
    35  	"k8s.io/kubernetes/test/e2e/framework"
    36  	imageutils "k8s.io/kubernetes/test/utils/image"
    37  	admissionapi "k8s.io/pod-security-admission/api"
    38  
    39  	"github.com/google/go-cmp/cmp"
    40  	"github.com/onsi/ginkgo/v2"
    41  	"github.com/onsi/gomega"
    42  	"k8s.io/cli-runtime/pkg/printers"
    43  	e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
    44  )
    45  
    46  var _ = SIGDescribe("MirrorPod", func() {
    47  	f := framework.NewDefaultFramework("mirror-pod")
    48  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    49  	ginkgo.Context("when create a mirror pod ", func() {
    50  		var ns, podPath, staticPodName, mirrorPodName string
    51  		ginkgo.BeforeEach(func(ctx context.Context) {
    52  			ns = f.Namespace.Name
    53  			staticPodName = "static-pod-" + string(uuid.NewUUID())
    54  			mirrorPodName = staticPodName + "-" + framework.TestContext.NodeName
    55  
    56  			podPath = kubeletCfg.StaticPodPath
    57  
    58  			ginkgo.By("create the static pod")
    59  			err := createStaticPod(podPath, staticPodName, ns,
    60  				imageutils.GetE2EImage(imageutils.Nginx), v1.RestartPolicyAlways)
    61  			framework.ExpectNoError(err)
    62  
    63  			ginkgo.By("wait for the mirror pod to be running")
    64  			gomega.Eventually(ctx, func(ctx context.Context) error {
    65  				return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
    66  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
    67  		})
    68  		/*
    69  			Release: v1.9
    70  			Testname: Mirror Pod, update
    71  			Description: Updating a static Pod MUST recreate an updated mirror Pod. Create a static pod, verify that a mirror pod is created. Update the static pod by changing the container image, the mirror pod MUST be re-created and updated with the new image.
    72  		*/
    73  		f.It("should be updated when static pod updated", f.WithNodeConformance(), func(ctx context.Context) {
    74  			ginkgo.By("get mirror pod uid")
    75  			pod, err := f.ClientSet.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
    76  			framework.ExpectNoError(err)
    77  			uid := pod.UID
    78  
    79  			ginkgo.By("update the static pod container image")
    80  			image := imageutils.GetPauseImageName()
    81  			err = createStaticPod(podPath, staticPodName, ns, image, v1.RestartPolicyAlways)
    82  			framework.ExpectNoError(err)
    83  
    84  			ginkgo.By("wait for the mirror pod to be updated")
    85  			gomega.Eventually(ctx, func(ctx context.Context) error {
    86  				return checkMirrorPodRecreatedAndRunning(ctx, f.ClientSet, mirrorPodName, ns, uid)
    87  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
    88  
    89  			ginkgo.By("check the mirror pod container image is updated")
    90  			pod, err = f.ClientSet.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
    91  			framework.ExpectNoError(err)
    92  			gomega.Expect(pod.Spec.Containers).To(gomega.HaveLen(1))
    93  			gomega.Expect(pod.Spec.Containers[0].Image).To(gomega.Equal(image))
    94  		})
    95  		/*
    96  			Release: v1.9
    97  			Testname: Mirror Pod, delete
    98  			Description:  When a mirror-Pod is deleted then the mirror pod MUST be re-created. Create a static pod, verify that a mirror pod is created. Delete the mirror pod, the mirror pod MUST be re-created and running.
    99  		*/
   100  		f.It("should be recreated when mirror pod gracefully deleted", f.WithNodeConformance(), func(ctx context.Context) {
   101  			ginkgo.By("get mirror pod uid")
   102  			pod, err := f.ClientSet.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
   103  			framework.ExpectNoError(err)
   104  			uid := pod.UID
   105  
   106  			ginkgo.By("delete the mirror pod with grace period 30s")
   107  			err = f.ClientSet.CoreV1().Pods(ns).Delete(ctx, mirrorPodName, *metav1.NewDeleteOptions(30))
   108  			framework.ExpectNoError(err)
   109  
   110  			ginkgo.By("wait for the mirror pod to be recreated")
   111  			gomega.Eventually(ctx, func(ctx context.Context) error {
   112  				return checkMirrorPodRecreatedAndRunning(ctx, f.ClientSet, mirrorPodName, ns, uid)
   113  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
   114  		})
   115  		/*
   116  			Release: v1.9
   117  			Testname: Mirror Pod, force delete
   118  			Description: When a mirror-Pod is deleted, forcibly, then the mirror pod MUST be re-created. Create a static pod, verify that a mirror pod is created. Delete the mirror pod with delete wait time set to zero forcing immediate deletion, the mirror pod MUST be re-created and running.
   119  		*/
   120  		f.It("should be recreated when mirror pod forcibly deleted", f.WithNodeConformance(), func(ctx context.Context) {
   121  			ginkgo.By("get mirror pod uid")
   122  			pod, err := f.ClientSet.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
   123  			framework.ExpectNoError(err)
   124  			uid := pod.UID
   125  
   126  			ginkgo.By("delete the mirror pod with grace period 0s")
   127  			err = f.ClientSet.CoreV1().Pods(ns).Delete(ctx, mirrorPodName, *metav1.NewDeleteOptions(0))
   128  			framework.ExpectNoError(err)
   129  
   130  			ginkgo.By("wait for the mirror pod to be recreated")
   131  			gomega.Eventually(ctx, func(ctx context.Context) error {
   132  				return checkMirrorPodRecreatedAndRunning(ctx, f.ClientSet, mirrorPodName, ns, uid)
   133  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
   134  		})
   135  		ginkgo.AfterEach(func(ctx context.Context) {
   136  			ginkgo.By("delete the static pod")
   137  			err := deleteStaticPod(podPath, staticPodName, ns)
   138  			framework.ExpectNoError(err)
   139  
   140  			ginkgo.By("wait for the mirror pod to disappear")
   141  			gomega.Eventually(ctx, func(ctx context.Context) error {
   142  				return checkMirrorPodDisappear(ctx, f.ClientSet, mirrorPodName, ns)
   143  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
   144  		})
   145  	})
   146  	ginkgo.Context("when create a mirror pod without changes ", func() {
   147  		var ns, podPath, staticPodName, mirrorPodName string
   148  		ginkgo.BeforeEach(func() {
   149  		})
   150  		/*
   151  			Release: v1.23
   152  			Testname: Mirror Pod, recreate
   153  			Description: When a static pod's manifest is removed and readded, the mirror pod MUST successfully recreate. Create the static pod, verify it is running, remove its manifest and then add it back, and verify the static pod runs again.
   154  		*/
   155  		f.It("should successfully recreate when file is removed and recreated", f.WithNodeConformance(), func(ctx context.Context) {
   156  			ns = f.Namespace.Name
   157  			staticPodName = "static-pod-" + string(uuid.NewUUID())
   158  			mirrorPodName = staticPodName + "-" + framework.TestContext.NodeName
   159  
   160  			podPath = kubeletCfg.StaticPodPath
   161  			ginkgo.By("create the static pod")
   162  			err := createStaticPod(podPath, staticPodName, ns,
   163  				imageutils.GetE2EImage(imageutils.Nginx), v1.RestartPolicyAlways)
   164  			framework.ExpectNoError(err)
   165  
   166  			ginkgo.By("wait for the mirror pod to be running")
   167  			gomega.Eventually(ctx, func(ctx context.Context) error {
   168  				return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
   169  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
   170  
   171  			ginkgo.By("delete the pod manifest from disk")
   172  			err = deleteStaticPod(podPath, staticPodName, ns)
   173  			framework.ExpectNoError(err)
   174  
   175  			ginkgo.By("recreate the file")
   176  			err = createStaticPod(podPath, staticPodName, ns,
   177  				imageutils.GetE2EImage(imageutils.Nginx), v1.RestartPolicyAlways)
   178  			framework.ExpectNoError(err)
   179  
   180  			ginkgo.By("mirror pod should restart with count 1")
   181  			gomega.Eventually(ctx, func(ctx context.Context) error {
   182  				return checkMirrorPodRunningWithRestartCount(ctx, 2*time.Second, 2*time.Minute, f.ClientSet, mirrorPodName, ns, 1)
   183  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
   184  
   185  			ginkgo.By("mirror pod should stay running")
   186  			gomega.Consistently(ctx, func(ctx context.Context) error {
   187  				return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
   188  			}, time.Second*30, time.Second*4).Should(gomega.BeNil())
   189  
   190  			ginkgo.By("delete the static pod")
   191  			err = deleteStaticPod(podPath, staticPodName, ns)
   192  			framework.ExpectNoError(err)
   193  
   194  			ginkgo.By("wait for the mirror pod to disappear")
   195  			gomega.Eventually(ctx, func(ctx context.Context) error {
   196  				return checkMirrorPodDisappear(ctx, f.ClientSet, mirrorPodName, ns)
   197  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
   198  		})
   199  	})
   200  	ginkgo.Context("when recreating a static pod", func() {
   201  		var ns, podPath, staticPodName, mirrorPodName string
   202  		f.It("it should launch successfully even if it temporarily failed termination due to volume failing to unmount", f.WithNodeConformance(), f.WithSerial(), func(ctx context.Context) {
   203  			node := getNodeName(ctx, f)
   204  			ns = f.Namespace.Name
   205  			c := f.ClientSet
   206  			nfsTestConfig, nfsServerPod, nfsServerHost := e2evolume.NewNFSServerWithNodeName(ctx, c, ns, []string{"-G", "777", "/exports"}, node)
   207  			ginkgo.DeferCleanup(func(ctx context.Context) {
   208  				framework.Logf("Cleaning up NFS server pod")
   209  				e2evolume.TestServerCleanup(ctx, f, nfsTestConfig)
   210  			})
   211  
   212  			podPath = kubeletCfg.StaticPodPath
   213  			staticPodName = "static-pod-nfs-test-pod" + string(uuid.NewUUID())
   214  			mirrorPodName = staticPodName + "-" + framework.TestContext.NodeName
   215  
   216  			ginkgo.By(fmt.Sprintf("Creating nfs test pod: %s", staticPodName))
   217  
   218  			err := createStaticPodUsingNfs(nfsServerHost, node, "sleep 999999", podPath, staticPodName, ns)
   219  			framework.ExpectNoError(err)
   220  			ginkgo.By(fmt.Sprintf("Wating for nfs test pod: %s to start running...", staticPodName))
   221  			gomega.Eventually(func() error {
   222  				return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
   223  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
   224  
   225  			mirrorPod, err := c.CoreV1().Pods(ns).Get(ctx, mirrorPodName, metav1.GetOptions{})
   226  			framework.ExpectNoError(err)
   227  
   228  			hash, ok := mirrorPod.Annotations[kubetypes.ConfigHashAnnotationKey]
   229  			if !ok || hash == "" {
   230  				framework.Failf("Failed to get hash for mirrorPod")
   231  			}
   232  
   233  			ginkgo.By("Stopping the NFS server")
   234  			stopNfsServer(f, nfsServerPod)
   235  
   236  			ginkgo.By("Waiting for NFS server to stop...")
   237  			time.Sleep(30 * time.Second)
   238  
   239  			ginkgo.By(fmt.Sprintf("Deleting the static nfs test pod: %s", staticPodName))
   240  			err = deleteStaticPod(podPath, staticPodName, ns)
   241  			framework.ExpectNoError(err)
   242  
   243  			// Wait 5 mins for syncTerminatedPod to fail. We expect that the pod volume should not be cleaned up because the NFS server is down.
   244  			gomega.Consistently(func() bool {
   245  				return podVolumeDirectoryExists(types.UID(hash))
   246  			}, 5*time.Minute, 10*time.Second).Should(gomega.BeTrue(), "pod volume should exist while nfs server is stopped")
   247  
   248  			ginkgo.By("Start the NFS server")
   249  			restartNfsServer(f, nfsServerPod)
   250  
   251  			ginkgo.By("Waiting for the pod volume to deleted after the NFS server is started")
   252  			gomega.Eventually(func() bool {
   253  				return podVolumeDirectoryExists(types.UID(hash))
   254  			}, 5*time.Minute, 10*time.Second).Should(gomega.BeFalse(), "pod volume should be deleted after nfs server is started")
   255  
   256  			// Create the static pod again with the same config and expect it to start running
   257  			err = createStaticPodUsingNfs(nfsServerHost, node, "sleep 999999", podPath, staticPodName, ns)
   258  			framework.ExpectNoError(err)
   259  			ginkgo.By(fmt.Sprintf("Wating for nfs test pod: %s to start running (after being recreated)", staticPodName))
   260  			gomega.Eventually(func() error {
   261  				return checkMirrorPodRunning(ctx, f.ClientSet, mirrorPodName, ns)
   262  			}, 5*time.Minute, 5*time.Second).Should(gomega.BeNil())
   263  		})
   264  
   265  		ginkgo.AfterEach(func(ctx context.Context) {
   266  			ginkgo.By("delete the static pod")
   267  			err := deleteStaticPod(podPath, staticPodName, ns)
   268  			framework.ExpectNoError(err)
   269  
   270  			ginkgo.By("wait for the mirror pod to disappear")
   271  			gomega.Eventually(ctx, func(ctx context.Context) error {
   272  				return checkMirrorPodDisappear(ctx, f.ClientSet, mirrorPodName, ns)
   273  			}, 2*time.Minute, time.Second*4).Should(gomega.BeNil())
   274  
   275  		})
   276  
   277  	})
   278  
   279  })
   280  
   281  func podVolumeDirectoryExists(uid types.UID) bool {
   282  	podVolumePath := fmt.Sprintf("/var/lib/kubelet/pods/%s/volumes/", uid)
   283  	var podVolumeDirectoryExists bool
   284  
   285  	if _, err := os.Stat(podVolumePath); !os.IsNotExist(err) {
   286  		podVolumeDirectoryExists = true
   287  	}
   288  
   289  	return podVolumeDirectoryExists
   290  }
   291  
   292  // Restart the passed-in nfs-server by issuing a `/usr/sbin/rpc.nfsd 1` command in the
   293  // pod's (only) container. This command changes the number of nfs server threads from
   294  // (presumably) zero back to 1, and therefore allows nfs to open connections again.
   295  func restartNfsServer(f *framework.Framework, serverPod *v1.Pod) {
   296  	const startcmd = "/usr/sbin/rpc.nfsd 1"
   297  	_, _, err := e2evolume.PodExec(f, serverPod, startcmd)
   298  	framework.ExpectNoError(err)
   299  
   300  }
   301  
   302  // Stop the passed-in nfs-server by issuing a `/usr/sbin/rpc.nfsd 0` command in the
   303  // pod's (only) container. This command changes the number of nfs server threads to 0,
   304  // thus closing all open nfs connections.
   305  func stopNfsServer(f *framework.Framework, serverPod *v1.Pod) {
   306  	const stopcmd = "/usr/sbin/rpc.nfsd 0"
   307  	_, _, err := e2evolume.PodExec(f, serverPod, stopcmd)
   308  	framework.ExpectNoError(err)
   309  }
   310  
   311  func createStaticPodUsingNfs(nfsIP string, nodeName string, cmd string, dir string, name string, ns string) error {
   312  	ginkgo.By("create pod using nfs volume")
   313  
   314  	isPrivileged := true
   315  	cmdLine := []string{"-c", cmd}
   316  	pod := &v1.Pod{
   317  		TypeMeta: metav1.TypeMeta{
   318  			Kind:       "Pod",
   319  			APIVersion: "v1",
   320  		},
   321  		ObjectMeta: metav1.ObjectMeta{
   322  			Name:      name,
   323  			Namespace: ns,
   324  		},
   325  		Spec: v1.PodSpec{
   326  			NodeName: nodeName,
   327  			Containers: []v1.Container{
   328  				{
   329  					Name:    "pod-nfs-vol",
   330  					Image:   imageutils.GetE2EImage(imageutils.BusyBox),
   331  					Command: []string{"/bin/sh"},
   332  					Args:    cmdLine,
   333  					VolumeMounts: []v1.VolumeMount{
   334  						{
   335  							Name:      "nfs-vol",
   336  							MountPath: "/mnt",
   337  						},
   338  					},
   339  					SecurityContext: &v1.SecurityContext{
   340  						Privileged: &isPrivileged,
   341  					},
   342  				},
   343  			},
   344  			RestartPolicy: v1.RestartPolicyNever, //don't restart pod
   345  			Volumes: []v1.Volume{
   346  				{
   347  					Name: "nfs-vol",
   348  					VolumeSource: v1.VolumeSource{
   349  						NFS: &v1.NFSVolumeSource{
   350  							Server:   nfsIP,
   351  							Path:     "/",
   352  							ReadOnly: false,
   353  						},
   354  					},
   355  				},
   356  			},
   357  		},
   358  	}
   359  
   360  	file := staticPodPath(dir, name, ns)
   361  	f, err := os.OpenFile(file, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0666)
   362  	if err != nil {
   363  		return err
   364  	}
   365  	defer f.Close()
   366  
   367  	y := printers.YAMLPrinter{}
   368  	y.PrintObj(pod, f)
   369  
   370  	return nil
   371  }
   372  
   373  func staticPodPath(dir, name, namespace string) string {
   374  	return filepath.Join(dir, namespace+"-"+name+".yaml")
   375  }
   376  
   377  func createStaticPod(dir, name, namespace, image string, restart v1.RestartPolicy) error {
   378  	template := `
   379  apiVersion: v1
   380  kind: Pod
   381  metadata:
   382    name: %s
   383    namespace: %s
   384  spec:
   385    containers:
   386    - name: test
   387      image: %s
   388    restartPolicy: %s
   389  `
   390  	file := staticPodPath(dir, name, namespace)
   391  	podYaml := fmt.Sprintf(template, name, namespace, image, string(restart))
   392  
   393  	f, err := os.OpenFile(file, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0666)
   394  	if err != nil {
   395  		return err
   396  	}
   397  	defer f.Close()
   398  
   399  	_, err = f.WriteString(podYaml)
   400  	return err
   401  }
   402  
   403  func deleteStaticPod(dir, name, namespace string) error {
   404  	file := staticPodPath(dir, name, namespace)
   405  	return os.Remove(file)
   406  }
   407  
   408  func checkMirrorPodDisappear(ctx context.Context, cl clientset.Interface, name, namespace string) error {
   409  	_, err := cl.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
   410  	if apierrors.IsNotFound(err) {
   411  		return nil
   412  	}
   413  	if err == nil {
   414  		return fmt.Errorf("mirror pod %v/%v still exists", namespace, name)
   415  	}
   416  	return fmt.Errorf("expect mirror pod %v/%v to not exist but got error: %w", namespace, name, err)
   417  }
   418  
   419  func checkMirrorPodRunning(ctx context.Context, cl clientset.Interface, name, namespace string) error {
   420  	pod, err := cl.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
   421  	if err != nil {
   422  		return fmt.Errorf("expected the mirror pod %q to appear: %w", name, err)
   423  	}
   424  	if pod.Status.Phase != v1.PodRunning {
   425  		return fmt.Errorf("expected the mirror pod %q to be running, got %q", name, pod.Status.Phase)
   426  	}
   427  	for i := range pod.Status.ContainerStatuses {
   428  		if pod.Status.ContainerStatuses[i].State.Running == nil {
   429  			return fmt.Errorf("expected the mirror pod %q with container %q to be running (got containers=%v)", name, pod.Status.ContainerStatuses[i].Name, pod.Status.ContainerStatuses[i].State)
   430  		}
   431  	}
   432  	return validateMirrorPod(ctx, cl, pod)
   433  }
   434  
   435  func checkMirrorPodRunningWithRestartCount(ctx context.Context, interval time.Duration, timeout time.Duration, cl clientset.Interface, name, namespace string, count int32) error {
   436  	var pod *v1.Pod
   437  	var err error
   438  	err = wait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (bool, error) {
   439  		pod, err = cl.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
   440  		if err != nil {
   441  			return false, fmt.Errorf("expected the mirror pod %q to appear: %w", name, err)
   442  		}
   443  		if pod.Status.Phase != v1.PodRunning {
   444  			return false, fmt.Errorf("expected the mirror pod %q to be running, got %q", name, pod.Status.Phase)
   445  		}
   446  		for i := range pod.Status.ContainerStatuses {
   447  			if pod.Status.ContainerStatuses[i].State.Waiting != nil {
   448  				// retry if pod is in waiting state
   449  				return false, nil
   450  			}
   451  			if pod.Status.ContainerStatuses[i].State.Running == nil {
   452  				return false, fmt.Errorf("expected the mirror pod %q with container %q to be running (got containers=%v)", name, pod.Status.ContainerStatuses[i].Name, pod.Status.ContainerStatuses[i].State)
   453  			}
   454  			if pod.Status.ContainerStatuses[i].RestartCount == count {
   455  				// found the restart count
   456  				return true, nil
   457  			}
   458  		}
   459  		return false, nil
   460  	})
   461  	if err != nil {
   462  		return err
   463  	}
   464  	return validateMirrorPod(ctx, cl, pod)
   465  }
   466  
   467  func checkMirrorPodRecreatedAndRunning(ctx context.Context, cl clientset.Interface, name, namespace string, oUID types.UID) error {
   468  	pod, err := cl.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
   469  	if err != nil {
   470  		return fmt.Errorf("expected the mirror pod %q to appear: %w", name, err)
   471  	}
   472  	if pod.UID == oUID {
   473  		return fmt.Errorf("expected the uid of mirror pod %q to be changed, got %q", name, pod.UID)
   474  	}
   475  	if pod.Status.Phase != v1.PodRunning {
   476  		return fmt.Errorf("expected the mirror pod %q to be running, got %q", name, pod.Status.Phase)
   477  	}
   478  	return validateMirrorPod(ctx, cl, pod)
   479  }
   480  
   481  func validateMirrorPod(ctx context.Context, cl clientset.Interface, mirrorPod *v1.Pod) error {
   482  	hash, ok := mirrorPod.Annotations[kubetypes.ConfigHashAnnotationKey]
   483  	if !ok || hash == "" {
   484  		return fmt.Errorf("expected mirror pod %q to have a hash annotation", mirrorPod.Name)
   485  	}
   486  	mirrorHash, ok := mirrorPod.Annotations[kubetypes.ConfigMirrorAnnotationKey]
   487  	if !ok || mirrorHash == "" {
   488  		return fmt.Errorf("expected mirror pod %q to have a mirror pod annotation", mirrorPod.Name)
   489  	}
   490  	if hash != mirrorHash {
   491  		return fmt.Errorf("expected mirror pod %q to have a matching mirror pod hash: got %q; expected %q", mirrorPod.Name, mirrorHash, hash)
   492  	}
   493  	source, ok := mirrorPod.Annotations[kubetypes.ConfigSourceAnnotationKey]
   494  	if !ok {
   495  		return fmt.Errorf("expected mirror pod %q to have a source annotation", mirrorPod.Name)
   496  	}
   497  	if source == kubetypes.ApiserverSource {
   498  		return fmt.Errorf("expected mirror pod %q source to not be 'api'; got: %q", mirrorPod.Name, source)
   499  	}
   500  
   501  	if len(mirrorPod.OwnerReferences) != 1 {
   502  		return fmt.Errorf("expected mirror pod %q to have a single owner reference: got %d", mirrorPod.Name, len(mirrorPod.OwnerReferences))
   503  	}
   504  	node, err := cl.CoreV1().Nodes().Get(ctx, framework.TestContext.NodeName, metav1.GetOptions{})
   505  	if err != nil {
   506  		return fmt.Errorf("failed to fetch test node: %w", err)
   507  	}
   508  
   509  	controller := true
   510  	expectedOwnerRef := metav1.OwnerReference{
   511  		APIVersion: "v1",
   512  		Kind:       "Node",
   513  		Name:       framework.TestContext.NodeName,
   514  		UID:        node.UID,
   515  		Controller: &controller,
   516  	}
   517  	ref := mirrorPod.OwnerReferences[0]
   518  	if !apiequality.Semantic.DeepEqual(ref, expectedOwnerRef) {
   519  		return fmt.Errorf("unexpected mirror pod %q owner ref: %v", mirrorPod.Name, cmp.Diff(expectedOwnerRef, ref))
   520  	}
   521  
   522  	return nil
   523  }