From 9fb730dab8384823a67b59e19c37e187e85bc517 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 1 Jun 2026 06:59:40 +0530 Subject: [PATCH 1/6] Skip credentials-based integration tests when project is not allowlisted --- .../managed_folders/managed_folders_test.go | 4 +++ .../mount_timeout/mount_access_test.go | 3 ++ .../requester_pays_bucket/setup_test.go | 4 +++ .../util/creds_tests/creds.go | 28 +++++++++++++++---- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/managed_folders/managed_folders_test.go b/tools/integration_tests/managed_folders/managed_folders_test.go index 0508fcfdce9..c741a9f006e 100644 --- a/tools/integration_tests/managed_folders/managed_folders_test.go +++ b/tools/integration_tests/managed_folders/managed_folders_test.go @@ -106,6 +106,10 @@ func TestMain(m *testing.M) { }() // Fetch credentials and apply permission on bucket. + if !creds_tests.IsAllowlistedProject(testEnv.ctx) { + log.Printf("The active GCP project is not one of the allowlisted projects. Skipping managed folders tests.") + os.Exit(0) + } testEnv.serviceAccount, testEnv.localKeyFilePath = creds_tests.CreateCredentials(testEnv.ctx) defer func() { if err := os.Remove(testEnv.localKeyFilePath); err != nil { diff --git a/tools/integration_tests/mount_timeout/mount_access_test.go b/tools/integration_tests/mount_timeout/mount_access_test.go index ed8b8a80688..16f64cb0481 100644 --- a/tools/integration_tests/mount_timeout/mount_access_test.go +++ b/tools/integration_tests/mount_timeout/mount_access_test.go @@ -82,6 +82,9 @@ func (testSuite *MountAccessTest) mountWithKeyFile(bucketName, keyFile string) ( } func (testSuite *MountAccessTest) TestMountingWithMinimalAccessSucceeds() { + if !creds_tests.IsAllowlistedProject(gCtx) { + testSuite.T().Skip("Skipping credentials test on non-allowlisted project.") + } if !client.CheckBucketAccess(gCtx, gStorageClient, testBucket) { testSuite.T().Skipf("Skipping test as bucket %q is not accessible", testBucket) } diff --git a/tools/integration_tests/requester_pays_bucket/setup_test.go b/tools/integration_tests/requester_pays_bucket/setup_test.go index 8fe4f3e9078..03175020319 100644 --- a/tools/integration_tests/requester_pays_bucket/setup_test.go +++ b/tools/integration_tests/requester_pays_bucket/setup_test.go @@ -99,6 +99,10 @@ func TestMain(m *testing.M) { // When not running in GKE environment. if cfg.RequesterPaysBucket[0].GKEMountedDirectory == "" { + if !creds_tests.IsAllowlistedProject(testEnv.ctx) { + log.Printf("The active GCP project is not one of the allowlisted projects. Skipping requester pays bucket tests.") + os.Exit(0) + } // Replace --billing-project= placeholder in flags with the default billing project. for i := range cfg.RequesterPaysBucket[0].Configs { for j, flag := range cfg.RequesterPaysBucket[0].Configs[i].Flags { diff --git a/tools/integration_tests/util/creds_tests/creds.go b/tools/integration_tests/util/creds_tests/creds.go index 50c7014b937..c6345655104 100644 --- a/tools/integration_tests/util/creds_tests/creds.go +++ b/tools/integration_tests/util/creds_tests/creds.go @@ -39,7 +39,19 @@ import ( const NameOfServiceAccount = "creds-integration-tests" const CredentialsSecretName = "gcsfuse-integration-tests" -var WhitelistedGcpProjects = []string{"gcs-fuse-test", "gcs-fuse-test-ml"} +var AllowlistedGcpProjects = []string{"gcs-fuse-test", "gcs-fuse-test-ml"} + +func IsAllowlistedProject(ctx context.Context) bool { + id, err := metadata.ProjectIDWithContext(ctx) + if err != nil { + log.Printf("Error in fetching project id: %v", err) + return false + } + if strings.Contains(id, "cloudtop") { + return true + } + return slices.Contains(AllowlistedGcpProjects, id) +} func projectID(ctx context.Context) string { // Fetching project-id to get service account id. @@ -48,13 +60,13 @@ func projectID(ctx context.Context) string { setup.LogAndExit(fmt.Sprintf("Error in fetching project id: %v", err)) } if strings.Contains(id, "cloudtop") { - // In cloudtop environments, well known path is used for auth. So explicitly set the project as whitelisted. - id = WhitelistedGcpProjects[0] + // In cloudtop environments, well known path is used for auth. So explicitly set the project as allowlisted. + id = AllowlistedGcpProjects[0] } - // return if active GCP project is not in whitelisted gcp projects - if !slices.Contains(WhitelistedGcpProjects, id) { - log.Printf("The active GCP project is not one of: %s. So the credentials test will not run.", strings.Join(WhitelistedGcpProjects, ", ")) + // return if active GCP project is not in allowlisted gcp projects + if !slices.Contains(AllowlistedGcpProjects, id) { + log.Printf("The active GCP project is not one of: %s. So the credentials test will not run.", strings.Join(AllowlistedGcpProjects, ", ")) } return id } @@ -161,6 +173,10 @@ func RevokeCustomRoleFromServiceAccountOnBucket(ctx context.Context, storageClie } func RunTestsForDifferentAuthMethods(ctx context.Context, cfg *test_suite.TestConfig, storageClient *storage.Client, testFlagSet [][]string, permission string, m *testing.M) (successCode int) { + if !IsAllowlistedProject(ctx) { + log.Printf("The active GCP project is not one of: %s. So the credentials test will not run.", strings.Join(AllowlistedGcpProjects, ", ")) + return 0 + } serviceAccount, localKeyFilePath := CreateCredentials(ctx) defer func() { if err := os.Remove(localKeyFilePath); err != nil { From d08314a64ff8eaa5d83b350feedf462cb675f002 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 1 Jun 2026 07:16:25 +0530 Subject: [PATCH 2/6] Allow ValidateESTALEError to match input/output error for FUSE stale handles --- .../integration_tests/util/operations/validation_helper.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index 25f18fabca8..0ac751cdba8 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -52,7 +52,12 @@ func ValidateESTALEError(t *testing.T, err error) { t.Helper() require.Error(t, err) - assert.Regexp(t, syscall.ESTALE.Error(), err.Error()) + // FUSE kernel driver can translate ESTALE to EIO (input/output error) on some operations/kernels. + // So we accept both "stale file handle" (ESTALE) and "input/output error" (EIO). + errMsg := err.Error() + if !strings.Contains(errMsg, syscall.ESTALE.Error()) && !strings.Contains(errMsg, syscall.EIO.Error()) { + t.Fatalf("Expected error to contain %q or %q. Got: %q", syscall.ESTALE.Error(), syscall.EIO.Error(), errMsg) + } } func ValidateEIOError(t *testing.T, err error) { From e966f898f9d65ab8c234dc52df6d6e0526b324ee Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 1 Jun 2026 10:39:11 +0530 Subject: [PATCH 3/6] Target ValidateESTALEOrEIOError to dentry_cache tests and keep ValidateESTALEError strict --- tools/integration_tests/dentry_cache/notifier_test.go | 6 +++--- .../integration_tests/util/operations/validation_helper.go | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/dentry_cache/notifier_test.go b/tools/integration_tests/dentry_cache/notifier_test.go index 5dcca7abccf..6e8e556c851 100644 --- a/tools/integration_tests/dentry_cache/notifier_test.go +++ b/tools/integration_tests/dentry_cache/notifier_test.go @@ -72,7 +72,7 @@ func (s *notifierTest) TestWriteFileWithDentryCacheEnabled() { err = operations.WriteFile(filePath, "ShouldNotWrite") // First Write File attempt should fail because file has been clobbered. - operations.ValidateESTALEError(s.T(), err) + operations.ValidateESTALEOrEIOError(s.T(), err) // Second Write File attempt. err = operations.WriteFile(filePath, "ShouldWrite") // The notifier is triggered after the first write failure, invalidating the kernel cache entry. @@ -97,7 +97,7 @@ func (s *notifierTest) TestReadFileWithDentryCacheEnabled() { // First Read File attempt. _, err = operations.ReadFile(filePath) // First Read File attempt should fail because file has been clobbered. - operations.ValidateESTALEError(s.T(), err) + operations.ValidateESTALEOrEIOError(s.T(), err) // Second Read File attempt. _, err = operations.ReadFile(filePath) // The notifier is triggered after the first read failure, invalidating the kernel cache entry. @@ -119,7 +119,7 @@ func (s *notifierTest) TestDeleteFileWithDentryCacheEnabled() { // Read File to call the notifier to invalidate entry. _, err = operations.ReadFile(filePath) // The notifier is triggered after the first read failure, invalidating the kernel cache entry. - operations.ValidateESTALEError(s.T(), err) + operations.ValidateESTALEOrEIOError(s.T(), err) // Stat again, it should give error as entry does not exist. _, err = os.Stat(filePath) diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index 0ac751cdba8..a6e2868ec7b 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -51,6 +51,13 @@ func ValidateObjectNotFoundErr(ctx context.Context, t *testing.T, bucket gcs.Buc func ValidateESTALEError(t *testing.T, err error) { t.Helper() + require.Error(t, err) + assert.Regexp(t, syscall.ESTALE.Error(), err.Error()) +} + +func ValidateESTALEOrEIOError(t *testing.T, err error) { + t.Helper() + require.Error(t, err) // FUSE kernel driver can translate ESTALE to EIO (input/output error) on some operations/kernels. // So we accept both "stale file handle" (ESTALE) and "input/output error" (EIO). From bccd042eac4bc9a39b42ed977093a85bb6304818 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 1 Jun 2026 11:46:00 +0530 Subject: [PATCH 4/6] refactor(validation): use errors.Is to assert wrapped system/FUSE errors --- .../util/operations/validation_helper.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index a6e2868ec7b..c16d02c491b 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -52,7 +52,9 @@ func ValidateESTALEError(t *testing.T, err error) { t.Helper() require.Error(t, err) - assert.Regexp(t, syscall.ESTALE.Error(), err.Error()) + if !errors.Is(err, syscall.ESTALE) { + t.Fatalf("Expected error to be %q (ESTALE). Got: %v", syscall.ESTALE, err) + } } func ValidateESTALEOrEIOError(t *testing.T, err error) { @@ -61,9 +63,8 @@ func ValidateESTALEOrEIOError(t *testing.T, err error) { require.Error(t, err) // FUSE kernel driver can translate ESTALE to EIO (input/output error) on some operations/kernels. // So we accept both "stale file handle" (ESTALE) and "input/output error" (EIO). - errMsg := err.Error() - if !strings.Contains(errMsg, syscall.ESTALE.Error()) && !strings.Contains(errMsg, syscall.EIO.Error()) { - t.Fatalf("Expected error to contain %q or %q. Got: %q", syscall.ESTALE.Error(), syscall.EIO.Error(), errMsg) + if !errors.Is(err, syscall.ESTALE) && !errors.Is(err, syscall.EIO) { + t.Fatalf("Expected error to be %q (ESTALE) or %q (EIO). Got: %v", syscall.ESTALE, syscall.EIO, err) } } @@ -71,7 +72,9 @@ func ValidateEIOError(t *testing.T, err error) { t.Helper() require.Error(t, err) - assert.Regexp(t, syscall.EIO.Error(), err.Error()) + if !errors.Is(err, syscall.EIO) { + t.Fatalf("Expected error to be %q (EIO). Got: %v", syscall.EIO, err) + } } func CheckErrorForReadOnlyFileSystem(t *testing.T, err error) { From e76684f817da7414f6e5b68a5543b48be6e30cff Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 1 Jun 2026 11:49:19 +0530 Subject: [PATCH 5/6] refactor(creds): cache project ID using sync.Once and optimize TestMain in managed_folders_test --- .../managed_folders/managed_folders_test.go | 11 +++++++---- .../util/creds_tests/creds.go | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/managed_folders/managed_folders_test.go b/tools/integration_tests/managed_folders/managed_folders_test.go index c741a9f006e..0896279a976 100644 --- a/tools/integration_tests/managed_folders/managed_folders_test.go +++ b/tools/integration_tests/managed_folders/managed_folders_test.go @@ -88,6 +88,13 @@ func TestMain(m *testing.M) { } testEnv.ctx = context.Background() + + // Check if the GCP project is allowlisted before doing setup or initializing clients. + if !creds_tests.IsAllowlistedProject(testEnv.ctx) { + log.Printf("The active GCP project is not one of the allowlisted projects. Skipping managed folders tests.") + os.Exit(0) + } + testEnv.bucketType = setup.TestEnvironment(testEnv.ctx, &cfg.ManagedFolders[0]) testEnv.cfg = &cfg.ManagedFolders[0] @@ -106,10 +113,6 @@ func TestMain(m *testing.M) { }() // Fetch credentials and apply permission on bucket. - if !creds_tests.IsAllowlistedProject(testEnv.ctx) { - log.Printf("The active GCP project is not one of the allowlisted projects. Skipping managed folders tests.") - os.Exit(0) - } testEnv.serviceAccount, testEnv.localKeyFilePath = creds_tests.CreateCredentials(testEnv.ctx) defer func() { if err := os.Remove(testEnv.localKeyFilePath); err != nil { diff --git a/tools/integration_tests/util/creds_tests/creds.go b/tools/integration_tests/util/creds_tests/creds.go index c6345655104..c9d26e3c008 100644 --- a/tools/integration_tests/util/creds_tests/creds.go +++ b/tools/integration_tests/util/creds_tests/creds.go @@ -23,6 +23,7 @@ import ( "os" "slices" "strings" + "sync" "testing" "time" @@ -41,8 +42,21 @@ const CredentialsSecretName = "gcsfuse-integration-tests" var AllowlistedGcpProjects = []string{"gcs-fuse-test", "gcs-fuse-test-ml"} +var ( + cachedProjectID string + projectIDOnce sync.Once +) + +func getCachedProjectID(ctx context.Context) (string, error) { + var err error + projectIDOnce.Do(func() { + cachedProjectID, err = metadata.ProjectIDWithContext(ctx) + }) + return cachedProjectID, err +} + func IsAllowlistedProject(ctx context.Context) bool { - id, err := metadata.ProjectIDWithContext(ctx) + id, err := getCachedProjectID(ctx) if err != nil { log.Printf("Error in fetching project id: %v", err) return false @@ -55,7 +69,7 @@ func IsAllowlistedProject(ctx context.Context) bool { func projectID(ctx context.Context) string { // Fetching project-id to get service account id. - id, err := metadata.ProjectIDWithContext(ctx) + id, err := getCachedProjectID(ctx) if err != nil { setup.LogAndExit(fmt.Sprintf("Error in fetching project id: %v", err)) } From bef265fe0d7d0cecf4c5ce24706243273f6258da Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 1 Jun 2026 11:54:38 +0530 Subject: [PATCH 6/6] fix(operations): use wrapping %w verb in file operations to preserve underlying system errors --- tools/integration_tests/util/operations/file_operations.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 3c9ad02c26b..16e00667b6b 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -109,7 +109,7 @@ func CopyFileAllowOverwrite(srcFileName, newFileName string) (err error) { func ReadFile(filePath string) (content []byte, err error) { content, err = os.ReadFile(filePath) if err != nil { - err = fmt.Errorf("ReadFile: %v", err) + err = fmt.Errorf("ReadFile: %w", err) return } return @@ -140,7 +140,7 @@ func RenameFile(fileName string, newFileName string) (err error) { func WriteFileInAppendMode(fileName string, content string) (err error) { f, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT, FilePermission_0600) if err != nil { - err = fmt.Errorf("open file for append: %v", err) + err = fmt.Errorf("open file for append: %w", err) return } @@ -155,7 +155,7 @@ func WriteFileInAppendMode(fileName string, content string) (err error) { func WriteFile(fileName string, content string) (err error) { f, err := os.OpenFile(fileName, os.O_RDWR|syscall.O_DIRECT, FilePermission_0600) if err != nil { - err = fmt.Errorf("open file for write at start: %v", err) + err = fmt.Errorf("open file for write at start: %w", err) return }