sigs.k8s.io/cluster-api@v1.6.3/internal/test/envtest/environment.go (about)

     1  /*
     2  Copyright 2020 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 envtest
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"os"
    24  	"path"
    25  	"path/filepath"
    26  	goruntime "runtime"
    27  	"strconv"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/onsi/ginkgo/v2"
    33  	"github.com/pkg/errors"
    34  	admissionv1 "k8s.io/api/admissionregistration/v1"
    35  	corev1 "k8s.io/api/core/v1"
    36  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    37  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    38  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    39  	kerrors "k8s.io/apimachinery/pkg/util/errors"
    40  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    41  	"k8s.io/apimachinery/pkg/util/wait"
    42  	"k8s.io/client-go/kubernetes/scheme"
    43  	"k8s.io/client-go/rest"
    44  	"k8s.io/klog/v2"
    45  	"k8s.io/klog/v2/klogr"
    46  	ctrl "sigs.k8s.io/controller-runtime"
    47  	"sigs.k8s.io/controller-runtime/pkg/client"
    48  	"sigs.k8s.io/controller-runtime/pkg/envtest"
    49  	"sigs.k8s.io/controller-runtime/pkg/manager"
    50  	metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
    51  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    52  
    53  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    54  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    55  	bootstrapwebhooks "sigs.k8s.io/cluster-api/bootstrap/kubeadm/webhooks"
    56  	"sigs.k8s.io/cluster-api/cmd/clusterctl/log"
    57  	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
    58  	controlplanewebhooks "sigs.k8s.io/cluster-api/controlplane/kubeadm/webhooks"
    59  	addonsv1 "sigs.k8s.io/cluster-api/exp/addons/api/v1beta1"
    60  	addonswebhooks "sigs.k8s.io/cluster-api/exp/addons/webhooks"
    61  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    62  	ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1"
    63  	expipamwebhooks "sigs.k8s.io/cluster-api/exp/ipam/webhooks"
    64  	runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1"
    65  	expapiwebhooks "sigs.k8s.io/cluster-api/exp/webhooks"
    66  	"sigs.k8s.io/cluster-api/internal/test/builder"
    67  	internalwebhooks "sigs.k8s.io/cluster-api/internal/webhooks"
    68  	runtimewebhooks "sigs.k8s.io/cluster-api/internal/webhooks/runtime"
    69  	"sigs.k8s.io/cluster-api/util/kubeconfig"
    70  	"sigs.k8s.io/cluster-api/version"
    71  	"sigs.k8s.io/cluster-api/webhooks"
    72  )
    73  
    74  func init() {
    75  	klog.InitFlags(nil)
    76  	logger := klogr.New()
    77  	// Use klog as the internal logger for this envtest environment.
    78  	log.SetLogger(logger)
    79  	// Additionally force all of the controllers to use the Ginkgo logger.
    80  	ctrl.SetLogger(logger)
    81  	// Add logger for ginkgo.
    82  	klog.SetOutput(ginkgo.GinkgoWriter)
    83  
    84  	// Calculate the scheme.
    85  	utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme))
    86  	utilruntime.Must(admissionv1.AddToScheme(scheme.Scheme))
    87  	utilruntime.Must(clusterv1.AddToScheme(scheme.Scheme))
    88  	utilruntime.Must(bootstrapv1.AddToScheme(scheme.Scheme))
    89  	utilruntime.Must(expv1.AddToScheme(scheme.Scheme))
    90  	utilruntime.Must(addonsv1.AddToScheme(scheme.Scheme))
    91  	utilruntime.Must(controlplanev1.AddToScheme(scheme.Scheme))
    92  	utilruntime.Must(admissionv1.AddToScheme(scheme.Scheme))
    93  	utilruntime.Must(runtimev1.AddToScheme(scheme.Scheme))
    94  	utilruntime.Must(ipamv1.AddToScheme(scheme.Scheme))
    95  }
    96  
    97  // RunInput is the input for Run.
    98  type RunInput struct {
    99  	M                   *testing.M
   100  	ManagerUncachedObjs []client.Object
   101  	SetupIndexes        func(ctx context.Context, mgr ctrl.Manager)
   102  	SetupReconcilers    func(ctx context.Context, mgr ctrl.Manager)
   103  	SetupEnv            func(e *Environment)
   104  	MinK8sVersion       string
   105  }
   106  
   107  // Run executes the tests of the given testing.M in a test environment.
   108  // Note: The environment will be created in this func and should not be created before. This func takes a *Environment
   109  //
   110  //	because our tests require access to the *Environment. We use this field to make the created Environment available
   111  //	to the consumer.
   112  //
   113  // Note: Test environment creation can be skipped by setting the environment variable `CAPI_DISABLE_TEST_ENV`
   114  // to a non-empty value. This only makes sense when executing tests which don't require the test environment,
   115  // e.g. tests using only the fake client.
   116  // Note: It's possible to write a kubeconfig for the test environment to a file by setting `CAPI_TEST_ENV_KUBECONFIG`.
   117  // Note: It's possible to skip stopping the test env after the tests have been run by setting `CAPI_TEST_ENV_SKIP_STOP`
   118  // to a non-empty value.
   119  func Run(ctx context.Context, input RunInput) int {
   120  	if os.Getenv("CAPI_DISABLE_TEST_ENV") != "" {
   121  		klog.Info("Skipping test env start as CAPI_DISABLE_TEST_ENV is set")
   122  		return input.M.Run()
   123  	}
   124  
   125  	// Bootstrapping test environment
   126  	env := newEnvironment(input.ManagerUncachedObjs...)
   127  
   128  	if input.SetupIndexes != nil {
   129  		input.SetupIndexes(ctx, env.Manager)
   130  	}
   131  	if input.SetupReconcilers != nil {
   132  		input.SetupReconcilers(ctx, env.Manager)
   133  	}
   134  
   135  	// Start the environment.
   136  	env.start(ctx)
   137  
   138  	if kubeconfigPath := os.Getenv("CAPI_TEST_ENV_KUBECONFIG"); kubeconfigPath != "" {
   139  		klog.Infof("Writing test env kubeconfig to %q", kubeconfigPath)
   140  		config := kubeconfig.FromEnvTestConfig(env.Config, &clusterv1.Cluster{
   141  			ObjectMeta: metav1.ObjectMeta{Name: "test"},
   142  		})
   143  		if err := os.WriteFile(kubeconfigPath, config, 0600); err != nil {
   144  			panic(errors.Wrapf(err, "failed to write the test env kubeconfig"))
   145  		}
   146  	}
   147  
   148  	if input.MinK8sVersion != "" {
   149  		if err := version.CheckKubernetesVersion(env.Config, input.MinK8sVersion); err != nil {
   150  			fmt.Printf("[ERROR] Cannot run tests after failing version check: %v\n", err)
   151  			if err := env.stop(); err != nil {
   152  				fmt.Println("[WARNING] Failed to stop the test environment")
   153  			}
   154  			return 1
   155  		}
   156  	}
   157  
   158  	// Expose the environment.
   159  	input.SetupEnv(env)
   160  
   161  	// Run tests
   162  	code := input.M.Run()
   163  
   164  	if skipStop := os.Getenv("CAPI_TEST_ENV_SKIP_STOP"); skipStop != "" {
   165  		klog.Info("Skipping test env stop as CAPI_TEST_ENV_SKIP_STOP is set")
   166  		return code
   167  	}
   168  
   169  	// Tearing down the test environment
   170  	if err := env.stop(); err != nil {
   171  		panic(fmt.Sprintf("Failed to stop the test environment: %v", err))
   172  	}
   173  	return code
   174  }
   175  
   176  var (
   177  	cacheSyncBackoff = wait.Backoff{
   178  		Duration: 100 * time.Millisecond,
   179  		Factor:   1.5,
   180  		Steps:    8,
   181  		Jitter:   0.4,
   182  	}
   183  )
   184  
   185  // Environment encapsulates a Kubernetes local test environment.
   186  type Environment struct {
   187  	manager.Manager
   188  	client.Client
   189  	Config *rest.Config
   190  
   191  	env           *envtest.Environment
   192  	cancelManager context.CancelFunc
   193  }
   194  
   195  // newEnvironment creates a new environment spinning up a local api-server.
   196  //
   197  // This function should be called only once for each package you're running tests within,
   198  // usually the environment is initialized in a suite_test.go file within a `BeforeSuite` ginkgo block.
   199  func newEnvironment(uncachedObjs ...client.Object) *Environment {
   200  	// Get the root of the current file to use in CRD paths.
   201  	_, filename, _, _ := goruntime.Caller(0) //nolint:dogsled
   202  	root := path.Join(path.Dir(filename), "..", "..", "..")
   203  
   204  	// Create the test environment.
   205  	env := &envtest.Environment{
   206  		ErrorIfCRDPathMissing: true,
   207  		CRDDirectoryPaths: []string{
   208  			filepath.Join(root, "config", "crd", "bases"),
   209  			filepath.Join(root, "controlplane", "kubeadm", "config", "crd", "bases"),
   210  			filepath.Join(root, "bootstrap", "kubeadm", "config", "crd", "bases"),
   211  		},
   212  		CRDs: []*apiextensionsv1.CustomResourceDefinition{
   213  			builder.GenericBootstrapConfigCRD.DeepCopy(),
   214  			builder.GenericBootstrapConfigTemplateCRD.DeepCopy(),
   215  			builder.GenericControlPlaneCRD.DeepCopy(),
   216  			builder.GenericControlPlaneTemplateCRD.DeepCopy(),
   217  			builder.GenericInfrastructureMachineCRD.DeepCopy(),
   218  			builder.GenericInfrastructureMachineTemplateCRD.DeepCopy(),
   219  			builder.GenericInfrastructureMachinePoolCRD.DeepCopy(),
   220  			builder.GenericInfrastructureMachinePoolTemplateCRD.DeepCopy(),
   221  			builder.GenericInfrastructureClusterCRD.DeepCopy(),
   222  			builder.GenericInfrastructureClusterTemplateCRD.DeepCopy(),
   223  			builder.GenericRemediationCRD.DeepCopy(),
   224  			builder.GenericRemediationTemplateCRD.DeepCopy(),
   225  			builder.TestInfrastructureClusterTemplateCRD.DeepCopy(),
   226  			builder.TestInfrastructureClusterCRD.DeepCopy(),
   227  			builder.TestInfrastructureMachineTemplateCRD.DeepCopy(),
   228  			builder.TestInfrastructureMachinePoolCRD.DeepCopy(),
   229  			builder.TestInfrastructureMachinePoolTemplateCRD.DeepCopy(),
   230  			builder.TestInfrastructureMachineCRD.DeepCopy(),
   231  			builder.TestBootstrapConfigTemplateCRD.DeepCopy(),
   232  			builder.TestBootstrapConfigCRD.DeepCopy(),
   233  			builder.TestControlPlaneTemplateCRD.DeepCopy(),
   234  			builder.TestControlPlaneCRD.DeepCopy(),
   235  		},
   236  		// initialize webhook here to be able to test the envtest install via webhookOptions
   237  		// This should set LocalServingCertDir and LocalServingPort that are used below.
   238  		WebhookInstallOptions: initWebhookInstallOptions(),
   239  	}
   240  
   241  	if _, err := env.Start(); err != nil {
   242  		err = kerrors.NewAggregate([]error{err, env.Stop()})
   243  		panic(err)
   244  	}
   245  
   246  	// Localhost is used on MacOS to avoid Firewall warning popups.
   247  	host := "localhost"
   248  	if strings.EqualFold(os.Getenv("USE_EXISTING_CLUSTER"), "true") {
   249  		// 0.0.0.0 is required on Linux when using kind because otherwise the kube-apiserver running in kind
   250  		// is unable to reach the webhook, because the webhook would be only listening on 127.0.0.1.
   251  		// Somehow that's not an issue on MacOS.
   252  		if goruntime.GOOS == "linux" {
   253  			host = "0.0.0.0"
   254  		}
   255  	}
   256  
   257  	options := manager.Options{
   258  		Scheme: scheme.Scheme,
   259  		Metrics: metricsserver.Options{
   260  			BindAddress: "0",
   261  		},
   262  		Client: client.Options{
   263  			Cache: &client.CacheOptions{
   264  				DisableFor: uncachedObjs,
   265  			},
   266  		},
   267  		WebhookServer: webhook.NewServer(
   268  			webhook.Options{
   269  				Port:    env.WebhookInstallOptions.LocalServingPort,
   270  				CertDir: env.WebhookInstallOptions.LocalServingCertDir,
   271  				Host:    host,
   272  			},
   273  		),
   274  	}
   275  
   276  	mgr, err := ctrl.NewManager(env.Config, options)
   277  	if err != nil {
   278  		klog.Fatalf("Failed to start testenv manager: %v", err)
   279  	}
   280  
   281  	// Set minNodeStartupTimeout for Test, so it does not need to be at least 30s
   282  	internalwebhooks.SetMinNodeStartupTimeout(metav1.Duration{Duration: 1 * time.Millisecond})
   283  
   284  	if err := (&webhooks.Cluster{Client: mgr.GetClient()}).SetupWebhookWithManager(mgr); err != nil {
   285  		klog.Fatalf("unable to create webhook: %+v", err)
   286  	}
   287  	if err := (&webhooks.ClusterClass{Client: mgr.GetClient()}).SetupWebhookWithManager(mgr); err != nil {
   288  		klog.Fatalf("unable to create webhook: %+v", err)
   289  	}
   290  	if err := (&webhooks.Machine{}).SetupWebhookWithManager(mgr); err != nil {
   291  		klog.Fatalf("unable to create webhook: %+v", err)
   292  	}
   293  	if err := (&webhooks.MachineHealthCheck{}).SetupWebhookWithManager(mgr); err != nil {
   294  		klog.Fatalf("unable to create webhook: %+v", err)
   295  	}
   296  	if err := (&webhooks.Machine{}).SetupWebhookWithManager(mgr); err != nil {
   297  		klog.Fatalf("unable to create webhook: %+v", err)
   298  	}
   299  	if err := (&webhooks.MachineSet{}).SetupWebhookWithManager(mgr); err != nil {
   300  		klog.Fatalf("unable to create webhook: %+v", err)
   301  	}
   302  	if err := (&webhooks.MachineDeployment{}).SetupWebhookWithManager(mgr); err != nil {
   303  		klog.Fatalf("unable to create webhook: %+v", err)
   304  	}
   305  	if err := (&bootstrapwebhooks.KubeadmConfig{}).SetupWebhookWithManager(mgr); err != nil {
   306  		klog.Fatalf("unable to create webhook: %+v", err)
   307  	}
   308  	if err := (&bootstrapwebhooks.KubeadmConfigTemplate{}).SetupWebhookWithManager(mgr); err != nil {
   309  		klog.Fatalf("unable to create webhook: %+v", err)
   310  	}
   311  	if err := (&controlplanewebhooks.KubeadmControlPlaneTemplate{}).SetupWebhookWithManager(mgr); err != nil {
   312  		klog.Fatalf("unable to create webhook: %+v", err)
   313  	}
   314  	if err := (&controlplanewebhooks.KubeadmControlPlane{}).SetupWebhookWithManager(mgr); err != nil {
   315  		klog.Fatalf("unable to create webhook: %+v", err)
   316  	}
   317  	if err := (&addonswebhooks.ClusterResourceSet{}).SetupWebhookWithManager(mgr); err != nil {
   318  		klog.Fatalf("unable to create webhook for crs: %+v", err)
   319  	}
   320  	if err := (&addonswebhooks.ClusterResourceSetBinding{}).SetupWebhookWithManager(mgr); err != nil {
   321  		klog.Fatalf("unable to create webhook for ClusterResourceSetBinding: %+v", err)
   322  	}
   323  	if err := (&expapiwebhooks.MachinePool{}).SetupWebhookWithManager(mgr); err != nil {
   324  		klog.Fatalf("unable to create webhook for machinepool: %+v", err)
   325  	}
   326  	if err := (&runtimewebhooks.ExtensionConfig{}).SetupWebhookWithManager(mgr); err != nil {
   327  		klog.Fatalf("unable to create webhook for extensionconfig: %+v", err)
   328  	}
   329  	if err := (&expipamwebhooks.IPAddress{}).SetupWebhookWithManager(mgr); err != nil {
   330  		klog.Fatalf("unable to create webhook for ipaddress: %v", err)
   331  	}
   332  	if err := (&expipamwebhooks.IPAddressClaim{}).SetupWebhookWithManager(mgr); err != nil {
   333  		klog.Fatalf("unable to create webhook for ipaddressclaim: %v", err)
   334  	}
   335  
   336  	return &Environment{
   337  		Manager: mgr,
   338  		Client:  mgr.GetClient(),
   339  		Config:  mgr.GetConfig(),
   340  		env:     env,
   341  	}
   342  }
   343  
   344  // start starts the manager.
   345  func (e *Environment) start(ctx context.Context) {
   346  	ctx, cancel := context.WithCancel(ctx)
   347  	e.cancelManager = cancel
   348  
   349  	go func() {
   350  		fmt.Println("Starting the test environment manager")
   351  		if err := e.Manager.Start(ctx); err != nil {
   352  			panic(fmt.Sprintf("Failed to start the test environment manager: %v", err))
   353  		}
   354  	}()
   355  	<-e.Manager.Elected()
   356  	e.waitForWebhooks()
   357  }
   358  
   359  // stop stops the test environment.
   360  func (e *Environment) stop() error {
   361  	fmt.Println("Stopping the test environment")
   362  	e.cancelManager()
   363  	return e.env.Stop()
   364  }
   365  
   366  // waitForWebhooks waits for the webhook server to be available.
   367  func (e *Environment) waitForWebhooks() {
   368  	port := e.env.WebhookInstallOptions.LocalServingPort
   369  
   370  	klog.V(2).Infof("Waiting for webhook port %d to be open prior to running tests", port)
   371  	timeout := 1 * time.Second
   372  	for {
   373  		time.Sleep(1 * time.Second)
   374  		conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port)), timeout)
   375  		if err != nil {
   376  			klog.V(2).Infof("Webhook port is not ready, will retry in %v: %s", timeout, err)
   377  			continue
   378  		}
   379  		if err := conn.Close(); err != nil {
   380  			klog.V(2).Infof("Closing connection when testing if webhook port is ready failed: %v", err)
   381  		}
   382  		klog.V(2).Info("Webhook port is now open. Continuing with tests...")
   383  		return
   384  	}
   385  }
   386  
   387  // CreateKubeconfigSecret generates a new Kubeconfig secret from the envtest config.
   388  func (e *Environment) CreateKubeconfigSecret(ctx context.Context, cluster *clusterv1.Cluster) error {
   389  	return e.Create(ctx, kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(e.Config, cluster)))
   390  }
   391  
   392  // Cleanup deletes all the given objects.
   393  func (e *Environment) Cleanup(ctx context.Context, objs ...client.Object) error {
   394  	errs := []error{}
   395  	for _, o := range objs {
   396  		err := e.Client.Delete(ctx, o)
   397  		if apierrors.IsNotFound(err) {
   398  			continue
   399  		}
   400  		errs = append(errs, err)
   401  	}
   402  	return kerrors.NewAggregate(errs)
   403  }
   404  
   405  // CleanupAndWait deletes all the given objects and waits for the cache to be updated accordingly.
   406  //
   407  // NOTE: Waiting for the cache to be updated helps in preventing test flakes due to the cache sync delays.
   408  func (e *Environment) CleanupAndWait(ctx context.Context, objs ...client.Object) error {
   409  	if err := e.Cleanup(ctx, objs...); err != nil {
   410  		return err
   411  	}
   412  
   413  	// Makes sure the cache is updated with the deleted object
   414  	errs := []error{}
   415  	for _, o := range objs {
   416  		// Ignoring namespaces because in testenv the namespace cleaner is not running.
   417  		if o.GetObjectKind().GroupVersionKind().GroupKind() == corev1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
   418  			continue
   419  		}
   420  
   421  		oCopy := o.DeepCopyObject().(client.Object)
   422  		key := client.ObjectKeyFromObject(o)
   423  		err := wait.ExponentialBackoff(
   424  			cacheSyncBackoff,
   425  			func() (done bool, err error) {
   426  				if err := e.Get(ctx, key, oCopy); err != nil {
   427  					if apierrors.IsNotFound(err) {
   428  						return true, nil
   429  					}
   430  					return false, err
   431  				}
   432  				return false, nil
   433  			})
   434  		errs = append(errs, errors.Wrapf(err, "key %s, %s is not being deleted from the testenv client cache", o.GetObjectKind().GroupVersionKind().String(), key))
   435  	}
   436  	return kerrors.NewAggregate(errs)
   437  }
   438  
   439  // CreateAndWait creates the given object and waits for the cache to be updated accordingly.
   440  //
   441  // NOTE: Waiting for the cache to be updated helps in preventing test flakes due to the cache sync delays.
   442  func (e *Environment) CreateAndWait(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
   443  	if err := e.Client.Create(ctx, obj, opts...); err != nil {
   444  		return err
   445  	}
   446  
   447  	// Makes sure the cache is updated with the new object
   448  	objCopy := obj.DeepCopyObject().(client.Object)
   449  	key := client.ObjectKeyFromObject(obj)
   450  	if err := wait.ExponentialBackoff(
   451  		cacheSyncBackoff,
   452  		func() (done bool, err error) {
   453  			if err := e.Get(ctx, key, objCopy); err != nil {
   454  				if apierrors.IsNotFound(err) {
   455  					return false, nil
   456  				}
   457  				return false, err
   458  			}
   459  			return true, nil
   460  		}); err != nil {
   461  		return errors.Wrapf(err, "object %s, %s is not being added to the testenv client cache", obj.GetObjectKind().GroupVersionKind().String(), key)
   462  	}
   463  	return nil
   464  }
   465  
   466  // PatchAndWait creates or updates the given object using server-side apply and waits for the cache to be updated accordingly.
   467  //
   468  // NOTE: Waiting for the cache to be updated helps in preventing test flakes due to the cache sync delays.
   469  func (e *Environment) PatchAndWait(ctx context.Context, obj client.Object, opts ...client.PatchOption) error {
   470  	key := client.ObjectKeyFromObject(obj)
   471  	objCopy := obj.DeepCopyObject().(client.Object)
   472  	if err := e.GetAPIReader().Get(ctx, key, objCopy); err != nil {
   473  		if !apierrors.IsNotFound(err) {
   474  			return err
   475  		}
   476  	}
   477  	// Store old resource version, empty string if not found.
   478  	oldResourceVersion := objCopy.GetResourceVersion()
   479  
   480  	if err := e.Client.Patch(ctx, obj, client.Apply, opts...); err != nil {
   481  		return err
   482  	}
   483  
   484  	// Makes sure the cache is updated with the new object
   485  	if err := wait.ExponentialBackoff(
   486  		cacheSyncBackoff,
   487  		func() (done bool, err error) {
   488  			if err := e.Get(ctx, key, objCopy); err != nil {
   489  				if apierrors.IsNotFound(err) {
   490  					return false, nil
   491  				}
   492  				return false, err
   493  			}
   494  			if objCopy.GetResourceVersion() == oldResourceVersion {
   495  				return false, nil
   496  			}
   497  			return true, nil
   498  		}); err != nil {
   499  		return errors.Wrapf(err, "object %s, %s is not being added to or did not get updated in the testenv client cache", obj.GetObjectKind().GroupVersionKind().String(), key)
   500  	}
   501  	return nil
   502  }
   503  
   504  // CreateNamespace creates a new namespace with a generated name.
   505  func (e *Environment) CreateNamespace(ctx context.Context, generateName string) (*corev1.Namespace, error) {
   506  	ns := &corev1.Namespace{
   507  		ObjectMeta: metav1.ObjectMeta{
   508  			GenerateName: fmt.Sprintf("%s-", generateName),
   509  			Labels: map[string]string{
   510  				"testenv/original-name": generateName,
   511  			},
   512  		},
   513  	}
   514  	if err := e.Client.Create(ctx, ns); err != nil {
   515  		return nil, err
   516  	}
   517  
   518  	return ns, nil
   519  }