diff --git a/controller/controlplane/controller_reconciler_utils.go b/controller/controlplane/controller_reconciler_utils.go index f5b10fdcf..862ad4548 100644 --- a/controller/controlplane/controller_reconciler_utils.go +++ b/controller/controlplane/controller_reconciler_utils.go @@ -40,9 +40,9 @@ const numReplicasWhenNoDataPlane = 0 // ----------------------------------------------------------------------------- func (r *Reconciler) ensureIsMarkedScheduled( - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, ) bool { - _, present := k8sutils.GetCondition(ConditionTypeProvisioned, controlplane) + _, present := k8sutils.GetCondition(ConditionTypeProvisioned, cp) if !present { condition := k8sutils.NewCondition( ConditionTypeProvisioned, @@ -51,7 +51,7 @@ func (r *Reconciler) ensureIsMarkedScheduled( "ControlPlane resource is scheduled for provisioning", ) - k8sutils.SetCondition(condition, controlplane) + k8sutils.SetCondition(condition, cp) return true } @@ -62,11 +62,11 @@ func (r *Reconciler) ensureIsMarkedScheduled( // to carry on with the controlplane deployments reconciliation. // Information about the missing dataplane is stored in the controlplane status. func (r *Reconciler) ensureDataPlaneStatus( - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, dataplane *operatorv1beta1.DataPlane, ) (dataplaneIsSet bool) { - dataplaneIsSet = controlplane.Spec.DataPlane != nil && *controlplane.Spec.DataPlane == dataplane.Name - condition, present := k8sutils.GetCondition(ConditionTypeProvisioned, controlplane) + dataplaneIsSet = cp.Spec.DataPlane != nil && *cp.Spec.DataPlane == dataplane.Name + condition, present := k8sutils.GetCondition(ConditionTypeProvisioned, cp) newCondition := k8sutils.NewCondition( ConditionTypeProvisioned, @@ -83,7 +83,7 @@ func (r *Reconciler) ensureDataPlaneStatus( ) } if !present || condition.Status != newCondition.Status || condition.Reason != newCondition.Reason { - k8sutils.SetCondition(newCondition, controlplane) + k8sutils.SetCondition(newCondition, cp) } return dataplaneIsSet } @@ -94,16 +94,16 @@ func (r *Reconciler) ensureDataPlaneStatus( func (r *Reconciler) ensureDataPlaneConfiguration( ctx context.Context, - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, dataplaneServiceName string, ) error { changed := setControlPlaneEnvOnDataPlaneChange( - &controlplane.Spec.ControlPlaneOptions, - controlplane.Namespace, + &cp.Spec.ControlPlaneOptions, + cp.Namespace, dataplaneServiceName, ) if changed { - if err := r.Client.Update(ctx, controlplane); err != nil { + if err := r.Client.Update(ctx, cp); err != nil { return fmt.Errorf("failed updating ControlPlane's DataPlane: %w", err) } return nil @@ -247,13 +247,13 @@ func (r *Reconciler) ensureDeployment( func (r *Reconciler) ensureServiceAccount( ctx context.Context, - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, ) (createdOrModified bool, sa *corev1.ServiceAccount, err error) { serviceAccounts, err := k8sutils.ListServiceAccountsForOwner( ctx, r.Client, - controlplane.Namespace, - controlplane.UID, + cp.Namespace, + cp.UID, client.MatchingLabels{ consts.GatewayOperatorManagedByLabel: consts.ControlPlaneManagedLabelValue, }, @@ -270,8 +270,8 @@ func (r *Reconciler) ensureServiceAccount( return false, nil, errors.New("number of serviceAccounts reduced") } - generatedServiceAccount := k8sresources.GenerateNewServiceAccountForControlPlane(controlplane.Namespace, controlplane.Name) - k8sutils.SetOwnerForObject(generatedServiceAccount, controlplane) + generatedServiceAccount := k8sresources.GenerateNewServiceAccountForControlPlane(cp.Namespace, cp.Name) + k8sutils.SetOwnerForObject(generatedServiceAccount, cp) if count == 1 { var updated bool @@ -291,12 +291,12 @@ func (r *Reconciler) ensureServiceAccount( func (r *Reconciler) ensureClusterRole( ctx context.Context, - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, ) (createdOrUpdated bool, cr *rbacv1.ClusterRole, err error) { clusterRoles, err := k8sutils.ListClusterRoles( ctx, r.Client, - client.MatchingLabels(k8sutils.GetManagedByLabelSet(controlplane)), + client.MatchingLabels(k8sutils.GetManagedByLabelSet(cp)), ) if err != nil { return false, nil, err @@ -310,12 +310,12 @@ func (r *Reconciler) ensureClusterRole( return false, nil, errors.New("number of clusterRoles reduced") } - controlplaneContainer := k8sutils.GetPodContainerByName(&controlplane.Spec.Deployment.PodTemplateSpec.Spec, consts.ControlPlaneControllerContainerName) - generated, err := k8sresources.GenerateNewClusterRoleForControlPlane(controlplane.Name, controlplaneContainer.Image, r.DevelopmentMode) + controlplaneContainer := k8sutils.GetPodContainerByName(&cp.Spec.Deployment.PodTemplateSpec.Spec, consts.ControlPlaneControllerContainerName) + generated, err := k8sresources.GenerateNewClusterRoleForControlPlane(cp.Name, controlplaneContainer.Image, r.DevelopmentMode) if err != nil { return false, nil, err } - k8sutils.SetOwnerForObjectThroughLabels(generated, controlplane) + k8sutils.SetOwnerForObjectThroughLabels(generated, cp) if count == 1 { var ( @@ -343,7 +343,7 @@ func (r *Reconciler) ensureClusterRole( func (r *Reconciler) ensureClusterRoleBinding( ctx context.Context, - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, serviceAccountName string, clusterRoleName string, ) (createdOrUpdate bool, crb *rbacv1.ClusterRoleBinding, err error) { @@ -352,7 +352,7 @@ func (r *Reconciler) ensureClusterRoleBinding( clusterRoleBindings, err := k8sutils.ListClusterRoleBindings( ctx, r.Client, - client.MatchingLabels(k8sutils.GetManagedByLabelSet(controlplane)), + client.MatchingLabels(k8sutils.GetManagedByLabelSet(cp)), ) if err != nil { return false, nil, err @@ -366,8 +366,8 @@ func (r *Reconciler) ensureClusterRoleBinding( return false, nil, errors.New("number of clusterRoleBindings reduced") } - generated := k8sresources.GenerateNewClusterRoleBindingForControlPlane(controlplane.Namespace, controlplane.Name, serviceAccountName, clusterRoleName) - k8sutils.SetOwnerForObjectThroughLabels(generated, controlplane) + generated := k8sresources.GenerateNewClusterRoleBindingForControlPlane(cp.Namespace, cp.Name, serviceAccountName, clusterRoleName) + k8sutils.SetOwnerForObjectThroughLabels(generated, cp) if count == 1 { existing := &clusterRoleBindings[0] @@ -391,7 +391,7 @@ func (r *Reconciler) ensureClusterRoleBinding( ) updated, existing.ObjectMeta = k8sutils.EnsureObjectMetaIsUpdated(existing.ObjectMeta, generated.ObjectMeta) - if !k8sresources.ClusterRoleBindingContainsServiceAccount(existing, controlplane.Namespace, serviceAccountName) { + if !k8sresources.ClusterRoleBindingContainsServiceAccount(existing, cp.Namespace, serviceAccountName) { existing.Subjects = generated.Subjects updatedServiceAccount = true } @@ -413,7 +413,7 @@ func (r *Reconciler) ensureClusterRoleBinding( // ControlPlane and the DataPlane. func (r *Reconciler) ensureAdminMTLSCertificateSecret( ctx context.Context, - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, ) ( op.CreatedUpdatedOrNoop, *corev1.Secret, @@ -430,8 +430,8 @@ func (r *Reconciler) ensureAdminMTLSCertificateSecret( // this subject is arbitrary. data planes only care that client certificates are signed by the trusted CA, and will // accept a certificate with any subject return secrets.EnsureCertificate(ctx, - controlplane, - fmt.Sprintf("%s.%s", controlplane.Name, controlplane.Namespace), + cp, + fmt.Sprintf("%s.%s", cp.Name, cp.Namespace), k8stypes.NamespacedName{ Namespace: r.ClusterCASecretNamespace, Name: r.ClusterCASecretName, @@ -479,14 +479,11 @@ func (r *Reconciler) ensureAdmissionWebhookCertificateSecret( // returns nil if all of owned ClusterRoles successfully deleted (ok if no owned CRs or NotFound on deleting CRs). func (r *Reconciler) ensureOwnedClusterRolesDeleted( ctx context.Context, - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, ) (deletions bool, err error) { clusterRoles, err := k8sutils.ListClusterRoles( ctx, r.Client, - client.MatchingLabels{ - consts.GatewayOperatorManagedByLabel: consts.ControlPlaneManagedLabelValue, - consts.GatewayOperatorManagedByNameLabel: controlplane.Name, - }, + client.MatchingLabels(k8sutils.GetManagedByLabelSet(cp)), ) if err != nil { return false, err @@ -512,14 +509,11 @@ func (r *Reconciler) ensureOwnedClusterRolesDeleted( // returns nil if all of owned ClusterRoleBindings successfully deleted (ok if no owned CRBs or NotFound on deleting CRBs). func (r *Reconciler) ensureOwnedClusterRoleBindingsDeleted( ctx context.Context, - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, ) (deletions bool, err error) { clusterRoleBindings, err := k8sutils.ListClusterRoleBindings( ctx, r.Client, - client.MatchingLabels{ - consts.GatewayOperatorManagedByLabel: consts.ControlPlaneManagedLabelValue, - consts.GatewayOperatorManagedByNameLabel: controlplane.Name, - }, + client.MatchingLabels(k8sutils.GetManagedByLabelSet(cp)), ) if err != nil { return false, err @@ -544,10 +538,7 @@ func (r *Reconciler) ensureOwnedValidatingWebhookConfigurationDeleted(ctx contex validatingWebhookConfigurations, err := k8sutils.ListValidatingWebhookConfigurations( ctx, r.Client, - client.MatchingLabels{ - consts.GatewayOperatorManagedByLabel: consts.ControlPlaneManagedLabelValue, - consts.GatewayOperatorManagedByNameLabel: cp.Name, - }, + client.MatchingLabels(k8sutils.GetManagedByLabelSet(cp)), ) if err != nil { return false, fmt.Errorf("failed listing webhook configurations for owner: %w", err) @@ -570,20 +561,20 @@ func (r *Reconciler) ensureOwnedValidatingWebhookConfigurationDeleted(ctx contex func (r *Reconciler) ensureAdmissionWebhookService( ctx context.Context, cl client.Client, - controlPlane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, ) (op.CreatedUpdatedOrNoop, *corev1.Service, error) { - matchingLabels := k8sresources.GetManagedLabelForOwner(controlPlane) + matchingLabels := k8sresources.GetManagedLabelForOwner(cp) matchingLabels[consts.ControlPlaneServiceLabel] = consts.ControlPlaneServiceKindWebhook services, err := k8sutils.ListServicesForOwner( ctx, cl, - controlPlane.Namespace, - controlPlane.UID, + cp.Namespace, + cp.UID, matchingLabels, ) if err != nil { - return op.Noop, nil, fmt.Errorf("failed listing admission webhook Services for ControlPlane %s/%s: %w", controlPlane.Namespace, controlPlane.Name, err) + return op.Noop, nil, fmt.Errorf("failed listing admission webhook Services for ControlPlane %s/%s: %w", cp.Namespace, cp.Name, err) } count := len(services) @@ -594,7 +585,7 @@ func (r *Reconciler) ensureAdmissionWebhookService( return op.Noop, nil, errors.New("number of ControlPlane admission webhook Services reduced") } - generatedService, err := k8sresources.GenerateNewAdmissionWebhookServiceForControlPlane(controlPlane) + generatedService, err := k8sresources.GenerateNewAdmissionWebhookServiceForControlPlane(cp) if err != nil { return op.Noop, nil, err } @@ -631,7 +622,7 @@ func (r *Reconciler) ensureAdmissionWebhookService( func (r *Reconciler) ensureValidatingWebhookConfiguration( ctx context.Context, - controlplane *operatorv1beta1.ControlPlane, + cp *operatorv1beta1.ControlPlane, certSecret *corev1.Secret, webhookServiceName string, ) (op.CreatedUpdatedOrNoop, error) { @@ -640,7 +631,7 @@ func (r *Reconciler) ensureValidatingWebhookConfiguration( validatingWebhookConfigurations, err := k8sutils.ListValidatingWebhookConfigurations( ctx, r.Client, - client.MatchingLabels(k8sutils.GetManagedByLabelSet(controlplane)), + client.MatchingLabels(k8sutils.GetManagedByLabelSet(cp)), ) if err != nil { return op.Noop, err @@ -654,7 +645,7 @@ func (r *Reconciler) ensureValidatingWebhookConfiguration( return op.Noop, errors.New("number of validatingWebhookConfigurations reduced") } - cpContainer := k8sutils.GetPodContainerByName(&controlplane.Spec.Deployment.PodTemplateSpec.Spec, consts.ControlPlaneControllerContainerName) + cpContainer := k8sutils.GetPodContainerByName(&cp.Spec.Deployment.PodTemplateSpec.Spec, consts.ControlPlaneControllerContainerName) if cpContainer == nil { return op.Noop, errors.New("controller container not found") } @@ -664,12 +655,12 @@ func (r *Reconciler) ensureValidatingWebhookConfiguration( return op.Noop, errors.New("ca.crt not found in secret") } generatedWebhookConfiguration, err := k8sresources.GenerateValidatingWebhookConfigurationForControlPlane( - controlplane.Name, + cp.Name, cpContainer.Image, r.DevelopmentMode, admregv1.WebhookClientConfig{ Service: &admregv1.ServiceReference{ - Namespace: controlplane.Namespace, + Namespace: cp.Namespace, Name: webhookServiceName, Port: lo.ToPtr(int32(consts.ControlPlaneAdmissionWebhookListenPort)), }, @@ -679,7 +670,7 @@ func (r *Reconciler) ensureValidatingWebhookConfiguration( if err != nil { return op.Noop, fmt.Errorf("failed generating ControlPlane's ValidatingWebhookConfiguration: %w", err) } - k8sutils.SetOwnerForObjectThroughLabels(generatedWebhookConfiguration, controlplane) + k8sutils.SetOwnerForObjectThroughLabels(generatedWebhookConfiguration, cp) if count == 1 { var updated bool diff --git a/controller/controlplane/controlplane_controller_reconciler_utils_test.go b/controller/controlplane/controlplane_controller_reconciler_utils_test.go index 9590ccfa6..58427548e 100644 --- a/controller/controlplane/controlplane_controller_reconciler_utils_test.go +++ b/controller/controlplane/controlplane_controller_reconciler_utils_test.go @@ -44,7 +44,36 @@ func TestEnsureClusterRole(t *testing.T) { wrongClusterRole2 := clusterRole.DeepCopy() wrongClusterRole2.ObjectMeta.Labels["aaa"] = "bbb" - clusterRole.ObjectMeta.Labels[consts.GatewayOperatorManagedByNameLabel] = "test-controlplane" + controlplane := operatorv1beta1.ControlPlane{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "gateway-operator.konghq.com/v1beta1", + Kind: "ControlPlane", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-controlplane", + Namespace: "test-namespace", + UID: types.UID(uuid.NewString()), + }, + Spec: operatorv1beta1.ControlPlaneSpec{ + ControlPlaneOptions: operatorv1beta1.ControlPlaneOptions{ + Deployment: operatorv1beta1.ControlPlaneDeploymentOptions{ + PodTemplateSpec: &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: consts.ControlPlaneControllerContainerName, + Image: consts.DefaultControlPlaneImage, + }, + }, + }, + }, + }, + }, + }, + } + + k8sutils.SetOwnerForObjectThroughLabels(clusterRole, &controlplane) + testCases := []struct { Name string controlplane operatorv1beta1.ControlPlane @@ -54,98 +83,20 @@ func TestEnsureClusterRole(t *testing.T) { err error }{ { - Name: "no existing clusterrole", - controlplane: operatorv1beta1.ControlPlane{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "gateway-operator.konghq.com/v1beta1", - Kind: "ControlPlane", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-controlplane", - Namespace: "test-namespace", - UID: types.UID(uuid.NewString()), - }, - Spec: operatorv1beta1.ControlPlaneSpec{ - ControlPlaneOptions: operatorv1beta1.ControlPlaneOptions{ - Deployment: operatorv1beta1.ControlPlaneDeploymentOptions{ - PodTemplateSpec: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: consts.ControlPlaneControllerContainerName, - Image: consts.DefaultControlPlaneImage, - }, - }, - }, - }, - }, - }, - }, - }, + Name: "no existing clusterrole", + controlplane: controlplane, createdorUpdated: true, expectedClusterRole: *clusterRole, }, { - Name: "up to date clusterrole", - controlplane: operatorv1beta1.ControlPlane{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "gateway-operator.konghq.com/v1beta1", - Kind: "ControlPlane", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-controlplane", - Namespace: "test-namespace", - UID: types.UID(uuid.NewString()), - }, - Spec: operatorv1beta1.ControlPlaneSpec{ - ControlPlaneOptions: operatorv1beta1.ControlPlaneOptions{ - Deployment: operatorv1beta1.ControlPlaneDeploymentOptions{ - PodTemplateSpec: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: consts.ControlPlaneControllerContainerName, - Image: consts.DefaultControlPlaneImage, - }, - }, - }, - }, - }, - }, - }, - }, + Name: "up to date clusterrole", + controlplane: controlplane, existingClusterRole: clusterRole, expectedClusterRole: *clusterRole, }, { - Name: "out of date clusterrole, object meta", - controlplane: operatorv1beta1.ControlPlane{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "gateway-operator.konghq.com/v1beta1", - Kind: "ControlPlane", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "test-controlplane", - Namespace: "test-namespace", - UID: types.UID(uuid.NewString()), - }, - Spec: operatorv1beta1.ControlPlaneSpec{ - ControlPlaneOptions: operatorv1beta1.ControlPlaneOptions{ - Deployment: operatorv1beta1.ControlPlaneDeploymentOptions{ - PodTemplateSpec: &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: consts.ControlPlaneControllerContainerName, - Image: consts.DefaultControlPlaneImage, - }, - }, - }, - }, - }, - }, - }, - }, + Name: "out of date clusterrole, object meta", + controlplane: controlplane, existingClusterRole: wrongClusterRole2, createdorUpdated: true, expectedClusterRole: *clusterRole, diff --git a/pkg/utils/test/asserts.go b/pkg/utils/test/asserts.go index 922b9b855..c53f36c4e 100644 --- a/pkg/utils/test/asserts.go +++ b/pkg/utils/test/asserts.go @@ -44,10 +44,7 @@ func MustListControlPlaneClusterRoles(t *testing.T, ctx context.Context, control clusterRoles, err := k8sutils.ListClusterRoles( ctx, clients.MgrClient, - client.MatchingLabels{ - consts.GatewayOperatorManagedByLabel: consts.ControlPlaneManagedLabelValue, - consts.GatewayOperatorManagedByNameLabel: controlplane.Name, - }, + client.MatchingLabels(k8sutils.GetManagedByLabelSet(controlplane)), ) require.NoError(t, err) return clusterRoles @@ -59,10 +56,7 @@ func MustListControlPlaneClusterRoleBindings(t *testing.T, ctx context.Context, clusterRoleBindings, err := k8sutils.ListClusterRoleBindings( ctx, clients.MgrClient, - client.MatchingLabels{ - consts.GatewayOperatorManagedByLabel: consts.ControlPlaneManagedLabelValue, - consts.GatewayOperatorManagedByNameLabel: controlplane.Name, - }, + client.MatchingLabels(k8sutils.GetManagedByLabelSet(controlplane)), ) require.NoError(t, err) return clusterRoleBindings diff --git a/pkg/utils/test/predicates.go b/pkg/utils/test/predicates.go index 830ea4e35..06f50d7a1 100644 --- a/pkg/utils/test/predicates.go +++ b/pkg/utils/test/predicates.go @@ -306,10 +306,7 @@ func ControlPlaneHasAdmissionWebhookCertificateSecret(t *testing.T, ctx context. // that can be used to check if a ControlPlane has an admission webhook configuration. func ControlPlaneHasAdmissionWebhookConfiguration(t *testing.T, ctx context.Context, cp *operatorv1beta1.ControlPlane, clients K8sClients) func() bool { return func() bool { - configs, err := k8sutils.ListValidatingWebhookConfigurations(ctx, clients.MgrClient, client.MatchingLabels{ - consts.GatewayOperatorManagedByLabel: consts.ControlPlaneManagedLabelValue, - consts.GatewayOperatorManagedByNameLabel: cp.Name, - }) + configs, err := k8sutils.ListValidatingWebhookConfigurations(ctx, clients.MgrClient, client.MatchingLabels(k8sutils.GetManagedByLabelSet(cp))) require.NoError(t, err) t.Logf("%d validating webhook configurations", len(configs)) return len(configs) > 0 diff --git a/test/integration/run_integration_test.go b/test/integration/run_integration_test.go index 9813a0276..8f5d48d99 100644 --- a/test/integration/run_integration_test.go +++ b/test/integration/run_integration_test.go @@ -20,7 +20,8 @@ func TestMain(m *testing.M) { helpers.SetDefaultDataPlaneImage(consts.DefaultDataPlaneImage) helpers.SetDefaultDataPlaneBaseImage(consts.DefaultDataPlaneBaseImage) - testSuiteToRun = helpers.ParseGoTestFlags(TestIntegration, testSuiteToRun) + //testSuiteToRun = helpers.ParseGoTestFlags(TestIntegration, testSuiteToRun) + testSuiteToRun = []func(*testing.T){integration.TestControlPlaneEssentials} cfg := integration.DefaultControllerConfigForTests() managerToTest := func(startedChan chan struct{}) error { return manager.Run(cfg, scheme.Get(), manager.SetupControllersShim, admission.NewRequestHandler, startedChan) diff --git a/test/integration/test_controlplane.go b/test/integration/test_controlplane.go index 60f4f968d..a5fac7d4d 100644 --- a/test/integration/test_controlplane.go +++ b/test/integration/test_controlplane.go @@ -256,6 +256,10 @@ func TestControlPlaneEssentials(t *testing.T) { controlplane, err = controlplaneClient.Create(GetCtx(), controlplane, metav1.CreateOptions{}) require.NoError(t, err) cleaner.Add(controlplane) + controlplane.TypeMeta = metav1.TypeMeta{ + APIVersion: operatorv1beta1.SchemeGroupVersion.String(), + Kind: "ControlPlane", + } t.Log("verifying controlplane gets marked scheduled") require.Eventually(t, testutils.ControlPlaneIsScheduled(t, GetCtx(), controlplaneName, GetClients().OperatorClient), testutils.ControlPlaneCondDeadline, testutils.ControlPlaneCondTick)