Repository: kubernetes/kubernetes
File: staging/src/k8s.io/apiextensions-apiserver/test/integration/finalization_test.go (Go)

Code before:
		_, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), noxuDefinition.Name, metav1.GetOptions{})
		return errors.IsNotFound(err), err
	})
	if !errors.IsNotFound(err) {
		t.Fatalf("unable to delete crd: %v", err)
	}
}

func TestApplyCRDuringCRDFinalization(t *testing.T) {
	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
	require.NoError(t, err)
	defer tearDown()

	// Create a CRD with a finalizer which will stall deletion
	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
	noxuDefinition.SetFinalizers([]string{"noxu.example.com/finalizer"})
	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
	require.NoError(t, err)

	// Delete the CRD. Since it has a finalizer it will be stuck in terminating state
	err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Delete(t.Context(), noxuDefinition.Name, metav1.DeleteOptions{})
	require.NoError(t, err)

	// Wait for the CRD to have a DeletionTimestamp, confirming it's in terminating state.
	// This avoids flakiness on slower systems where the deletion might not be processed
	// before we attempt the apply.
	err = wait.PollUntilContextTimeout(t.Context(), 100*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) {
		crd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, noxuDefinition.Name, metav1.GetOptions{})
		if err != nil {
			return false, err
		}
		return crd.DeletionTimestamp != nil, nil
	})
	require.NoError(t, err, "timed out waiting for CRD to enter terminating state")

	// Try to create a CR using SSA. This should fail due to the CRD validation
	ns := "not-the-default"
	name := "foo123"
	noxuResourceClient := newNamespacedCustomResourceClient(ns, dynamicClient, noxuDefinition)

	instance := fixtures.NewNoxuInstance(ns, name)
	_, err = noxuResourceClient.Apply(t.Context(), name, instance, metav1.ApplyOptions{DryRun: []string{"All"}, FieldManager: "manager"})
	wantErr := `create not allowed while custom resource definition is terminating`
	require.ErrorContains(t, err, wantErr)
}


Review comment (performance):
Why does "have a DeletionTimestamp" equal "it's in terminating state"? What does "terminating state" mean?

This doesn't really change anything except slowing down the `Apply` by one `Get`. The entire `wait.PollUntilContextTimeout` is useless because the first `Get` will always return something which has the `DeletionTimestamp`.

Code after:
	_, err = noxuResourceClient.Get(context.TODO(), name, metav1.GetOptions{})
	if !errors.IsNotFound(err) {
		t.Fatalf("unable to delete cr: %v", err)
	}

	err = wait.Poll(500*time.Millisecond, wait.ForeverTestTimeout, func() (bool, error) {
		_, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), noxuDefinition.Name, metav1.GetOptions{})
		return errors.IsNotFound(err), err
	})
	if !errors.IsNotFound(err) {
		t.Fatalf("unable to delete crd: %v", err)
	}
}

func TestApplyCRDuringCRDFinalization(t *testing.T) {
	tearDown, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
	require.NoError(t, err)
	defer tearDown()

	// Create a CRD with a finalizer which will stall deletion
	noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped)
	noxuDefinition.SetFinalizers([]string{testFinalizer})
	noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, dynamicClient)
	require.NoError(t, err)

	// Delete the CRD. Since it has a finalizer it will be stuck in terminating state
	err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Delete(t.Context(), noxuDefinition.Name, metav1.DeleteOptions{})
	require.NoError(t, err)

	// Wait for the CRD to have the Terminating condition set to True.
	// The handler checks IsCRDConditionTrue(crd, apiextensionsv1.Terminating) to block
	// CR creation, and this condition is set asynchronously by the CRD finalizer controller
	// after it observes the DeletionTimestamp. Without this wait, the Apply could succeed
	// if it races ahead of the controller setting the condition.
	err = wait.PollUntilContextTimeout(t.Context(), 100*time.Millisecond, wait.ForeverTestTimeout, true, func(ctx context.Context) (bool, error) {
		crd, err := apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, noxuDefinition.Name, metav1.GetOptions{})
		if err != nil {
			return false, err
		}
		return apiextensionshelpers.IsCRDConditionTrue(crd, apiextensionsv1.Terminating), nil
	})
	require.NoError(t, err, "timed out waiting for CRD Terminating condition to be set")

	// Try to create a CR using SSA. This should fail due to the CRD validation
	ns := testNamespace
	name := "foo123"
	noxuResourceClient := newNamespacedCustomResourceClient(ns, dynamicClient, noxuDefinition)

	instance := fixtures.NewNoxuInstance(ns, name)
	_, err = noxuResourceClient.Apply(t.Context(), name, instance, metav1.ApplyOptions{DryRun: []string{"All"}, FieldManager: "manager"})
	wantErr := `create not allowed while custom resource definition is terminating`