From a658b24ac09ce3b752758cd08e6c026e04cc7ea6 Mon Sep 17 00:00:00 2001 From: Hadrien Patte Date: Fri, 13 Mar 2026 14:51:02 +0100 Subject: [PATCH 1/2] External-ccm: Update go to 1.25 Update go to 1.25, the currently used go 1.24 has stopped being supported when go 1.26 got released in February, see [go release policy](https://go.dev/doc/devel/release#policy). Signed-off-by: Hadrien Patte --- .github/workflows/makefile.yml | 4 ++-- Dockerfile | 2 +- Dockerfile_arm_all | 2 +- go.mod | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index e345d182a..e23beba81 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 # Upgraded with: - go-version: '1.24.13' + go-version: '1.25.8' cache: true # Built-in caching for faster runs - name: Install dependencies @@ -39,4 +39,4 @@ jobs: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | go install github.com/mattn/goveralls@latest - $(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github \ No newline at end of file + $(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github diff --git a/Dockerfile b/Dockerfile index c1d36b44b..8f8412ea6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ ARG CI_IMAGE_REGISTRY -FROM golang:1.24.13 as builder +FROM golang:1.25.8 as builder ARG COMPONENT diff --git a/Dockerfile_arm_all b/Dockerfile_arm_all index 955e8e508..20244c93e 100644 --- a/Dockerfile_arm_all +++ b/Dockerfile_arm_all @@ -1,6 +1,6 @@ ARG CI_IMAGE_REGISTRY -FROM golang:1.24.13 as builder +FROM golang:1.25.8 as builder ARG COMPONENT diff --git a/go.mod b/go.mod index b741e40be..a362bbb92 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/oracle/oci-cloud-controller-manager -go 1.24.13 +go 1.25.0 replace ( github.com/docker/distribution => github.com/docker/distribution v2.8.3+incompatible From 7e2ea108489888acb6443fe684e94554501ff616 Mon Sep 17 00:00:00 2001 From: Hadrien Patte Date: Fri, 13 Mar 2026 15:29:52 +0100 Subject: [PATCH 2/2] External-ccm: Use `testing/synctest` to eliminate waits in tests `pkg/csi/driver` tests currently take more than 4 minutes to run and cause timeouts in CI (exit code 143, SIGTERM from runner). This is caused by 14 "timeout" sub-tests across `bv_controller_test.go` and `fss_controller_test.go` each blocking for a full testTimeout (15s) of real wall-clock time (~210s total). These tests verify that operations correctly propagate context cancellation when a volume or attachment gets stuck. The mocks simulate an infinite page stream using a non-blocking select with a default branch, causing the caller's pagination loop to spin-wait on the CPU until the context deadline elapses in real time. Since [go 1.25](https://go.dev/doc/go1.25#new-testingsynctest-package), go supports using fake time to reliabily test those scenarios without having to wait for the actual timeout when running the tests. See [Testing concurrent code with testing/synctest](https://go.dev/blog/synctest). This PR updates those tests to use `testing/synctest`: * Update go from 1.24 to 1.25 to have `testing/synctest` support. * Wrap each affected `t.Run` body in `synctest.Test()`, so `context.WithTimeout` and all mock calls run under a fake clock. * Change the five non-blocking `select { default: }` branches in `mock_oci_clients.go` to `select { case <-time.After(mockPollInterval): }`, making goroutines durably block so the fake clock can advance. * Move the shared `context.WithTimeout` in `fss_controller_test.go` inside each `t.Run` (it was incorrectly shared across sub-tests in a loop). Before: ``` ok github.com/oracle/oci-cloud-controller-manager/pkg/csi/driver 231.711s ``` After: ``` ok github.com/oracle/oci-cloud-controller-manager/pkg/csi/driver 1.933s ``` Result: `pkg/csi/driver` tests are 100x faster Signed-off-by: Hadrien Patte --- pkg/csi/driver/bv_controller_test.go | 141 ++++++++++++++------------ pkg/csi/driver/fss_controller_test.go | 45 ++++---- pkg/util/mock_oci_clients.go | 22 ++-- 3 files changed, 110 insertions(+), 98 deletions(-) diff --git a/pkg/csi/driver/bv_controller_test.go b/pkg/csi/driver/bv_controller_test.go index 865712141..f66b8d92f 100644 --- a/pkg/csi/driver/bv_controller_test.go +++ b/pkg/csi/driver/bv_controller_test.go @@ -21,6 +21,7 @@ import ( "reflect" "strings" "testing" + "testing/synctest" "time" "github.com/container-storage-interface/spec/lib/go/csi" @@ -1235,27 +1236,29 @@ func TestControllerDriver_CreateVolume(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - d := &BlockVolumeControllerDriver{ControllerDriver{ - KubeClient: nil, - logger: zap.S(), - config: &providercfg.Config{CompartmentID: ""}, - client: NewClientProvisioner(nil, &MockBlockStorageClient{}, nil), - util: &csi_util.Util{Logger: logging.Logger().Sugar()}, - }} - got, err := d.CreateVolume(ctx, tt.args.req) - if tt.wantErr == nil && err != nil { - t.Errorf("got error %q, want none", err) - } - if tt.wantErr != nil && err == nil { - t.Errorf("want error %q, got none", tt.wantErr) - } else if tt.wantErr != nil && !strings.Contains(err.Error(), tt.wantErr.Error()) { - t.Errorf("want error %q to include %q", err, tt.wantErr) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ControllerDriver.CreateVolume() = %v, want %v", got, tt.want) - } + synctest.Test(t, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + d := &BlockVolumeControllerDriver{ControllerDriver{ + KubeClient: nil, + logger: zap.S(), + config: &providercfg.Config{CompartmentID: ""}, + client: NewClientProvisioner(nil, &MockBlockStorageClient{}, nil), + util: &csi_util.Util{Logger: logging.Logger().Sugar()}, + }} + got, err := d.CreateVolume(ctx, tt.args.req) + if tt.wantErr == nil && err != nil { + t.Errorf("got error %q, want none", err) + } + if tt.wantErr != nil && err == nil { + t.Errorf("want error %q, got none", tt.wantErr) + } else if tt.wantErr != nil && !strings.Contains(err.Error(), tt.wantErr.Error()) { + t.Errorf("want error %q to include %q", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ControllerDriver.CreateVolume() = %v, want %v", got, tt.want) + } + }) }) } } @@ -1405,29 +1408,31 @@ func TestControllerDriver_ControllerPublishVolume(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - d := &BlockVolumeControllerDriver{ControllerDriver{ - KubeClient: &util.MockKubeClient{ - CoreClient: &util.MockCoreClient{}, - }, - logger: zap.S(), - config: &providercfg.Config{CompartmentID: ""}, - client: NewClientProvisioner(nil, &MockBlockStorageClient{}, nil), - util: &csi_util.Util{Logger: logging.Logger().Sugar()}, - }} - got, err := d.ControllerPublishVolume(ctx, tt.args.req) - if tt.wantErr == nil && err != nil { - t.Errorf("got error %q, want none", err) - } - if tt.wantErr != nil && err == nil { - t.Errorf("want error %q, got none", tt.wantErr) - } else if tt.wantErr != nil && !strings.Contains(err.Error(), tt.wantErr.Error()) { - t.Errorf("want error %q to include %q", err, tt.wantErr) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ControllerDriver.ControllerPublish() = %v, want %v", got, tt.want) - } + synctest.Test(t, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + d := &BlockVolumeControllerDriver{ControllerDriver{ + KubeClient: &util.MockKubeClient{ + CoreClient: &util.MockCoreClient{}, + }, + logger: zap.S(), + config: &providercfg.Config{CompartmentID: ""}, + client: NewClientProvisioner(nil, &MockBlockStorageClient{}, nil), + util: &csi_util.Util{Logger: logging.Logger().Sugar()}, + }} + got, err := d.ControllerPublishVolume(ctx, tt.args.req) + if tt.wantErr == nil && err != nil { + t.Errorf("got error %q, want none", err) + } + if tt.wantErr != nil && err == nil { + t.Errorf("want error %q, got none", tt.wantErr) + } else if tt.wantErr != nil && !strings.Contains(err.Error(), tt.wantErr.Error()) { + t.Errorf("want error %q to include %q", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ControllerDriver.ControllerPublish() = %v, want %v", got, tt.want) + } + }) }) } } @@ -1479,29 +1484,31 @@ func TestControllerDriver_ControllerUnpublishVolume(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() - d := &BlockVolumeControllerDriver{ControllerDriver{ - KubeClient: &util.MockKubeClient{ - CoreClient: &util.MockCoreClient{}, - }, - logger: zap.S(), - config: &providercfg.Config{CompartmentID: ""}, - client: NewClientProvisioner(nil, &MockBlockStorageClient{}, nil), - util: &csi_util.Util{Logger: logging.Logger().Sugar()}, - }} - got, err := d.ControllerUnpublishVolume(ctx, tt.args.req) - if tt.wantErr == nil && err != nil { - t.Errorf("got error %q, want none", err) - } - if tt.wantErr != nil && err == nil { - t.Errorf("want error %q, got none", tt.wantErr) - } else if tt.wantErr != nil && !strings.Contains(err.Error(), tt.wantErr.Error()) { - t.Errorf("want error %q to include %q", err, tt.wantErr) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ControllerDriver.ControllerUnpublish() = %v, want %v", got, tt.want) - } + synctest.Test(t, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + d := &BlockVolumeControllerDriver{ControllerDriver{ + KubeClient: &util.MockKubeClient{ + CoreClient: &util.MockCoreClient{}, + }, + logger: zap.S(), + config: &providercfg.Config{CompartmentID: ""}, + client: NewClientProvisioner(nil, &MockBlockStorageClient{}, nil), + util: &csi_util.Util{Logger: logging.Logger().Sugar()}, + }} + got, err := d.ControllerUnpublishVolume(ctx, tt.args.req) + if tt.wantErr == nil && err != nil { + t.Errorf("got error %q, want none", err) + } + if tt.wantErr != nil && err == nil { + t.Errorf("want error %q, got none", tt.wantErr) + } else if tt.wantErr != nil && !strings.Contains(err.Error(), tt.wantErr.Error()) { + t.Errorf("want error %q to include %q", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ControllerDriver.ControllerUnpublish() = %v, want %v", got, tt.want) + } + }) }) } } diff --git a/pkg/csi/driver/fss_controller_test.go b/pkg/csi/driver/fss_controller_test.go index 0c0411bfc..a03fbb64e 100644 --- a/pkg/csi/driver/fss_controller_test.go +++ b/pkg/csi/driver/fss_controller_test.go @@ -21,6 +21,7 @@ import ( "reflect" "strings" "testing" + "testing/synctest" "time" "github.com/container-storage-interface/spec/lib/go/csi" @@ -751,28 +752,30 @@ func TestFSSControllerDriver_CreateVolume(t *testing.T) { }, } for _, tt := range tests { - ctx, cancel := context.WithTimeout(context.Background(), testTimeout) - defer cancel() t.Run(tt.name, func(t *testing.T) { - d := &FSSControllerDriver{ControllerDriver: ControllerDriver{ - KubeClient: nil, - logger: zap.S(), - config: &providercfg.Config{CompartmentID: "", Auth: config.AuthConfig{TenancyID: tt.args.tenancyId}}, - client: NewClientProvisioner(nil, nil, &MockFileStorageClient{}), - util: &csi_util.Util{}, - }} - got, err := d.CreateVolume(ctx, tt.args.req) - if tt.wantErr == nil && err != nil { - t.Errorf("got error %q, want none", err) - } - if tt.wantErr != nil && err == nil { - t.Errorf("want error %q, got none", tt.wantErr) - } else if tt.wantErr != nil && !strings.Contains(err.Error(), tt.wantErr.Error()) { - t.Errorf("want error %q to include %q", err, tt.wantErr) - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("ControllerDriver.CreateVolume() = %v, want %v", got, tt.want) - } + synctest.Test(t, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), testTimeout) + defer cancel() + d := &FSSControllerDriver{ControllerDriver: ControllerDriver{ + KubeClient: nil, + logger: zap.S(), + config: &providercfg.Config{CompartmentID: "", Auth: config.AuthConfig{TenancyID: tt.args.tenancyId}}, + client: NewClientProvisioner(nil, nil, &MockFileStorageClient{}), + util: &csi_util.Util{}, + }} + got, err := d.CreateVolume(ctx, tt.args.req) + if tt.wantErr == nil && err != nil { + t.Errorf("got error %q, want none", err) + } + if tt.wantErr != nil && err == nil { + t.Errorf("want error %q, got none", tt.wantErr) + } else if tt.wantErr != nil && !strings.Contains(err.Error(), tt.wantErr.Error()) { + t.Errorf("want error %q to include %q", err, tt.wantErr) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ControllerDriver.CreateVolume() = %v, want %v", got, tt.want) + } + }) }) } } diff --git a/pkg/util/mock_oci_clients.go b/pkg/util/mock_oci_clients.go index 8e4a1cd92..106426dc0 100644 --- a/pkg/util/mock_oci_clients.go +++ b/pkg/util/mock_oci_clients.go @@ -2,6 +2,7 @@ package util import ( "context" + "time" "github.com/oracle/oci-go-sdk/v65/common" "github.com/oracle/oci-go-sdk/v65/core" @@ -9,6 +10,12 @@ import ( lustre "github.com/oracle/oci-go-sdk/v65/lustrefilestorage" ) +// mockPollInterval is the fake-time interval used by pagination mocks that +// simulate an infinite page stream until context cancellation. It must be +// smaller than testTimeout so the context expires after a few iterations when +// running inside a testing/synctest bubble. +const mockPollInterval = 5 * time.Second + type MockOCIBlockStorageClient struct { client core.BlockstorageClient } @@ -28,10 +35,9 @@ type MockOCILustreFileStorageClient struct { func (c MockOCIFileStorageClient) ListMountTargets(ctx context.Context, request filestorage.ListMountTargetsRequest) (response filestorage.ListMountTargetsResponse, err error) { if *request.DisplayName == "mount-target-idempotency-check-timeout-volume" { select { - // from retry.go case <-ctx.Done(): return response, ctx.Err() - default: + case <-time.After(mockPollInterval): return filestorage.ListMountTargetsResponse{ Items: []filestorage.MountTargetSummary{ { @@ -48,10 +54,9 @@ func (c MockOCIFileStorageClient) ListMountTargets(ctx context.Context, request func (c MockOCIFileStorageClient) ListFileSystems(ctx context.Context, request filestorage.ListFileSystemsRequest) (response filestorage.ListFileSystemsResponse, err error) { if *request.DisplayName == "file-system-idempotency-check-timeout-volume" { select { - // from retry.go case <-ctx.Done(): return response, ctx.Err() - default: + case <-time.After(mockPollInterval): return filestorage.ListFileSystemsResponse{ Items: []filestorage.FileSystemSummary{ { @@ -68,10 +73,9 @@ func (c MockOCIFileStorageClient) ListFileSystems(ctx context.Context, request f func (c MockOCIFileStorageClient) ListExports(ctx context.Context, request filestorage.ListExportsRequest) (response filestorage.ListExportsResponse, err error) { if *request.FileSystemId == "export-idempotency-check-timeout" { select { - // from retry.go case <-ctx.Done(): return response, ctx.Err() - default: + case <-time.After(mockPollInterval): return filestorage.ListExportsResponse{ Items: []filestorage.ExportSummary{}, OpcNextPage: common.String("a"), @@ -84,10 +88,9 @@ func (c MockOCIFileStorageClient) ListExports(ctx context.Context, request files func (c MockOCIComputeClient) ListVolumeAttachments(ctx context.Context, request core.ListVolumeAttachmentsRequest) (response core.ListVolumeAttachmentsResponse, err error) { if *request.VolumeId == "find-active-volume-attachment-timeout-volume" || *request.VolumeId == "find-volume-attachment-timeout" { select { - // from retry.go case <-ctx.Done(): return response, ctx.Err() - default: + case <-time.After(mockPollInterval): return core.ListVolumeAttachmentsResponse{ Items: []core.VolumeAttachment{ core.IScsiVolumeAttachment{ @@ -122,10 +125,9 @@ func (c MockOCIComputeClient) GetVolumeAttachment(ctx context.Context, request c func (c *MockOCIBlockStorageClient) ListVolumes(ctx context.Context, request core.ListVolumesRequest) (response core.ListVolumesResponse, err error) { if *request.DisplayName == "get-volumes-by-name-timeout-volume" { select { - // from retry.go case <-ctx.Done(): return response, ctx.Err() - default: + case <-time.After(mockPollInterval): return core.ListVolumesResponse{ Items: []core.Volume{ {