diff --git a/service/access_control_service_test.go b/service/access_control_service_test.go index 996350357..ae8270366 100644 --- a/service/access_control_service_test.go +++ b/service/access_control_service_test.go @@ -1270,7 +1270,7 @@ func ACS_ReconcileWorkerClusterServiceAccountAndRoleBindings(t *testing.T) { mMock.AssertExpectations(t) } -func prepareACSTestContext(ctx context.Context, client util.Client, +func prepareACSTestContext(ctx context.Context, client client.Client, scheme *runtime.Scheme) context.Context { if scheme == nil { scheme = runtime.NewScheme() diff --git a/service/namespace_service_test.go b/service/namespace_service_test.go index 42a15a513..fe350cb1c 100644 --- a/service/namespace_service_test.go +++ b/service/namespace_service_test.go @@ -177,7 +177,7 @@ func TestDeleteNamespace_DoesNothingIfNamespaceDoNotExist(t *testing.T) { mMock.AssertExpectations(t) } -func prepareNamespaceTestContext(ctx context.Context, client util.Client, scheme *runtime.Scheme) context.Context { +func prepareNamespaceTestContext(ctx context.Context, client client.Client, scheme *runtime.Scheme) context.Context { eventRecorder := events.NewEventRecorder(client, scheme, ossEvents.EventsMap, events.EventRecorderOptions{ Version: "v1alpha1", Cluster: util.ClusterController, diff --git a/service/project_service_test.go b/service/project_service_test.go index 21398c6cb..dfd4b2388 100644 --- a/service/project_service_test.go +++ b/service/project_service_test.go @@ -32,6 +32,7 @@ import ( ossEvents "github.com/kubeslice/kubeslice-controller/events" "github.com/kubeslice/kubeslice-controller/service/mocks" "github.com/kubeslice/kubeslice-controller/util" + "sigs.k8s.io/controller-runtime/pkg/client" utilMock "github.com/kubeslice/kubeslice-controller/util/mocks" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -276,7 +277,7 @@ func setupProjectTest(name string, namespace string) (*mocks.INamespaceService, return nsServiceMock, acsServicemOCK, projectService, requestObj, clientMock, project, ctx, clusterServiceMock, sliceConfigServiceMock, serviceExportConfigServiceMock, sliceQoSConfigServiceMock, mMock } -func prepareProjectTestContext(ctx context.Context, client util.Client, +func prepareProjectTestContext(ctx context.Context, client client.Client, scheme *runtime.Scheme) context.Context { eventRecorder := events.NewEventRecorder(client, scheme, ossEvents.EventsMap, events.EventRecorderOptions{ Version: "v1alpha1", diff --git a/service/vpn_cert_rotation_job_failure_bug_test.go b/service/vpn_cert_rotation_job_failure_bug_test.go new file mode 100644 index 000000000..0cf3fef61 --- /dev/null +++ b/service/vpn_cert_rotation_job_failure_bug_test.go @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2026 Avesha, Inc. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package service + +import ( + "testing" + "time" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + controllerv1alpha1 "github.com/kubeslice/kubeslice-controller/apis/controller/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +// Test_VpnCertRotationJobFailure_NoRequeue verifies that a failed cert-rotation Job +// causes the reconciler to requeue (not silently stop) and resets jobCreationInProgress +// so the job is recreated on the next reconcile. +func Test_VpnCertRotationJobFailure_NoRequeue(t *testing.T) { + ctx, clientMock, vpnService, _, _ := setupTestCase() + + // Certs are expired: CertificateExpiryTime is in the past. + expiredTime := metav1.NewTime(time.Now().Add(-1 * time.Hour)) + vpnConfig := &controllerv1alpha1.VpnKeyRotation{ + ObjectMeta: metav1.ObjectMeta{Name: "test-slice", Namespace: "test-ns"}, + Spec: controllerv1alpha1.VpnKeyRotationSpec{ + SliceName: "test-slice", + CertificateCreationTime: &expiredTime, + CertificateExpiryTime: &expiredTime, + RotationInterval: 30, + }, + } + sliceConfig := &controllerv1alpha1.SliceConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "test-slice", Namespace: "test-ns"}, + } + + // Simulate: jobs were already triggered in a previous reconcile. + vpnService.jobCreationInProgress.Store(true) + + // The cert-rotation Job is in Failed state. + clientMock. + On("List", mock.Anything, mock.AnythingOfType("*v1.JobList"), mock.Anything). + Return(nil). + Run(func(args mock.Arguments) { + jobList := args.Get(1).(*batchv1.JobList) + jobList.Items = []batchv1.Job{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "cert-job-1", + Labels: map[string]string{"SLICE_NAME": "test-slice"}, + }, + Status: batchv1.JobStatus{ + Conditions: []batchv1.JobCondition{ + {Type: batchv1.JobFailed, Status: corev1.ConditionTrue}, + }, + }, + }, + } + }).Once() + clientMock.On("Create", mock.Anything, mock.AnythingOfType("*v1.Event")).Return(nil).Maybe() + + result, returnedConfig, err := vpnService.reconcileVpnKeyRotationConfig(ctx, vpnConfig, sliceConfig) + + assert.Nil(t, err) + assert.Nil(t, returnedConfig) + assert.Equal(t, 30*time.Second, result.RequeueAfter, "must requeue so rotation is retried after job failure") + assert.False(t, vpnService.jobCreationInProgress.Load(), "jobCreationInProgress must be cleared so the failed job is recreated") +} diff --git a/service/vpn_key_rotation_service.go b/service/vpn_key_rotation_service.go index 7da8437ca..1c33e12ca 100644 --- a/service/vpn_key_rotation_service.go +++ b/service/vpn_key_rotation_service.go @@ -249,7 +249,8 @@ func (v *VpnKeyRotationService) reconcileVpnKeyRotationConfig(ctx context.Contex if status == JobStatusError || status == JobStatusSuspended { // register an event util.RecordEvent(ctx, eventRecorder, copyVpnConfig, nil, events.EventCertificateJobFailed) - return ctrl.Result{}, nil, nil + v.jobCreationInProgress.Store(false) + return ctrl.Result{RequeueAfter: 30 * time.Second}, nil, nil } if status == JobNotCreated { return ctrl.Result{RequeueAfter: 30 * time.Second}, nil, nil @@ -290,7 +291,8 @@ func (v *VpnKeyRotationService) reconcileVpnKeyRotationConfig(ctx context.Contex if status == JobStatusError || status == JobStatusSuspended { // register an event util.RecordEvent(ctx, eventRecorder, copyVpnConfig, nil, events.EventCertificateJobFailed) - return ctrl.Result{}, nil, nil + v.jobCreationInProgress.Store(false) + return ctrl.Result{RequeueAfter: 30 * time.Second}, nil, nil } if status == JobNotCreated { return ctrl.Result{RequeueAfter: 30 * time.Second}, nil, nil diff --git a/service/worker_slice_gateway_service_test.go b/service/worker_slice_gateway_service_test.go index f09906b8f..9b27dd97d 100644 --- a/service/worker_slice_gateway_service_test.go +++ b/service/worker_slice_gateway_service_test.go @@ -551,7 +551,6 @@ func testNodeIpReconciliationOfWorkerSliceGatewaysExists(t *testing.T) { Annotations: nil, OwnerReferences: nil, Finalizers: nil, - ClusterName: "", ManagedFields: nil, }, Spec: controllerv1alpha1.ClusterSpec{},